23964ea316f34df7f1abf0d7879c9750d65cfdb1
[oom/platform/cert-service.git] / certService / src / test / java / org / onap / oom / certservice / cmpv2client / Cmpv2ClientTest.java
1 /*
2  * Copyright (C) 2019 Ericsson Software Technology AB. All rights reserved.
3  * Copyright (C) 2021 Nokia. All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License
16  */
17
18 package org.onap.oom.certservice.cmpv2client;
19
20 import static org.assertj.core.api.Assertions.assertThat;
21 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
22 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.Mockito.doAnswer;
26 import static org.mockito.Mockito.spy;
27 import static org.mockito.Mockito.when;
28 import static org.mockito.MockitoAnnotations.initMocks;
29 import static org.onap.oom.certservice.cmpv2client.ClientTestData.createOldCertificateModelWithPrivateKeyInPkcs1;
30 import static org.onap.oom.certservice.cmpv2client.ClientTestData.createOldCertificateModelWithPrivateKeyInPkcs8;
31
32 import java.io.BufferedInputStream;
33 import java.io.ByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.security.KeyFactory;
39 import java.security.KeyPair;
40 import java.security.NoSuchAlgorithmException;
41 import java.security.NoSuchProviderException;
42 import java.security.PrivateKey;
43 import java.security.PublicKey;
44 import java.security.Security;
45
46 import java.security.spec.InvalidKeySpecException;
47 import java.security.spec.PKCS8EncodedKeySpec;
48 import java.security.spec.X509EncodedKeySpec;
49 import java.text.ParseException;
50 import java.text.SimpleDateFormat;
51 import java.util.Base64;
52 import java.util.Base64.Decoder;
53 import java.util.Date;
54
55 import java.util.stream.Stream;
56 import org.apache.commons.io.IOUtils;
57 import org.apache.http.HttpEntity;
58 import org.apache.http.client.methods.CloseableHttpResponse;
59 import org.apache.http.impl.client.CloseableHttpClient;
60 import org.bouncycastle.asn1.ASN1GeneralizedTime;
61 import org.bouncycastle.asn1.ASN1Integer;
62 import org.bouncycastle.asn1.DERBitString;
63 import org.bouncycastle.asn1.cmp.PKIBody;
64 import org.bouncycastle.asn1.cmp.PKIHeader;
65 import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
66 import org.bouncycastle.asn1.cmp.PKIMessage;
67 import org.bouncycastle.asn1.crmf.CertReqMessages;
68 import org.bouncycastle.asn1.crmf.CertReqMsg;
69 import org.bouncycastle.asn1.crmf.CertRequest;
70 import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
71 import org.bouncycastle.asn1.crmf.ProofOfPossession;
72 import org.bouncycastle.asn1.x500.X500Name;
73 import org.bouncycastle.asn1.x500.X500NameBuilder;
74 import org.bouncycastle.asn1.x500.style.BCStyle;
75 import org.bouncycastle.asn1.x509.GeneralName;
76 import org.bouncycastle.jce.provider.BouncyCastleProvider;
77 import org.junit.jupiter.api.Assertions;
78 import org.junit.jupiter.api.BeforeEach;
79 import org.junit.jupiter.api.Test;
80 import org.junit.jupiter.params.ParameterizedTest;
81 import org.junit.jupiter.params.provider.Arguments;
82 import org.junit.jupiter.params.provider.MethodSource;
83 import org.mockito.Mock;
84 import org.onap.oom.certservice.certification.configuration.model.Authentication;
85 import org.onap.oom.certservice.certification.configuration.model.Cmpv2Server;
86 import org.onap.oom.certservice.certification.exception.CertificateDecryptionException;
87 import org.onap.oom.certservice.certification.model.CsrModel;
88 import org.onap.oom.certservice.certification.model.OldCertificateModel;
89 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
90 import org.onap.oom.certservice.cmpv2client.exceptions.CmpServerException;
91 import org.onap.oom.certservice.cmpv2client.impl.CmpClientImpl;
92 import org.onap.oom.certservice.cmpv2client.model.Cmpv2CertificationModel;
93
94 class Cmpv2ClientTest {
95
96     static {
97         Security.addProvider(new BouncyCastleProvider());
98     }
99
100     private CsrModel csrModel;
101     private Cmpv2Server server;
102     private Date notBefore;
103     private Date notAfter;
104     private X500Name dn;
105
106
107     @Mock
108     CloseableHttpClient httpClient;
109
110     @Mock
111     CloseableHttpResponse httpResponse;
112
113     @Mock
114     HttpEntity httpEntity;
115
116     private static KeyPair keyPair;
117
118     private static final Decoder BASE64_DECODER = Base64.getDecoder();
119
120     @BeforeEach
121     void setUp()
122             throws NoSuchProviderException, NoSuchAlgorithmException, IOException,
123             InvalidKeySpecException {
124         keyPair = loadKeyPair();
125         dn = new X500NameBuilder()
126                 .addRDN(BCStyle.O, "TestOrganization")
127                 .build();
128         initMocks(this);
129     }
130
131     public KeyPair loadKeyPair()
132             throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
133             NoSuchProviderException {
134
135         final InputStream privateInputStream = this.getClass().getResourceAsStream("/privateKey");
136         final InputStream publicInputStream = this.getClass().getResourceAsStream("/publicKey");
137         BufferedInputStream bis = new BufferedInputStream(privateInputStream);
138         byte[] privateBytes = IOUtils.toByteArray(bis);
139         bis = new BufferedInputStream(publicInputStream);
140         byte[] publicBytes = IOUtils.toByteArray(bis);
141
142         KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
143         X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicBytes);
144         PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
145
146         PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateBytes);
147         PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
148
149         return new KeyPair(publicKey, privateKey);
150     }
151
152     @Test
153     void shouldReturnCorrectCmpCertificateForCorrectKeyUpdateResponse() throws CmpClientException, IOException, CertificateDecryptionException {
154
155         // given
156         setCsrModelAndServerTestDefaultValues();
157         when(httpClient.execute(any())).thenReturn(httpResponse);
158         when(httpResponse.getEntity()).thenReturn(httpEntity);
159
160         mockCorrectKeyUpdateResponse();
161         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
162
163         // when
164         Cmpv2CertificationModel cmpClientResult =
165             cmpClient.executeKeyUpdateRequest(csrModel, server, ClientTestData.createCorrectOldCertificateModel());
166
167         // then
168         assertNotNull(cmpClientResult);
169         assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
170         assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
171
172     }
173
174     @Test
175     void shouldReturnCorrectCmpCertificateForCorrectCertificationRequest() throws CmpClientException, IOException {
176
177         // given
178         setCsrModelAndServerTestDefaultValues();
179         when(httpClient.execute(any())).thenReturn(httpResponse);
180         when(httpResponse.getEntity()).thenReturn(httpEntity);
181
182         doAnswer(
183             invocation -> {
184                 OutputStream os = invocation.getArgument(0);
185                 os.write(BASE64_DECODER.decode(ClientTestData.CR_CORRECT_SERVER_RESPONSE_ENCODED.getBytes()));
186                 return null;
187             })
188             .when(httpEntity)
189             .writeTo(any(OutputStream.class));
190         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
191
192         // when
193         Cmpv2CertificationModel cmpClientResult =
194             cmpClient.executeCertificationRequest(csrModel, server);
195
196         // then
197         assertNotNull(cmpClientResult);
198         assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
199         assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
200
201     }
202
203     @ParameterizedTest
204     @MethodSource("getTestUpdateModelWithSupportedPrivateKeys")
205     void shouldNotThrowExceptionForPrivateKeyInExpectedFormat(OldCertificateModel oldCertificateModel)
206         throws IOException {
207
208         // given
209         setCsrModelAndServerTestDefaultValues();
210         when(httpClient.execute(any())).thenReturn(httpResponse);
211         when(httpResponse.getEntity()).thenReturn(httpEntity);
212
213         mockCorrectKeyUpdateResponse();
214         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
215
216         // when // then
217         assertDoesNotThrow(() -> cmpClient
218             .executeKeyUpdateRequest(csrModel, server, oldCertificateModel)
219         );
220
221     }
222
223     @Test
224     void shouldThrowCmpClientExceptionWhenCannotParseOldPrivateKey() {
225         setCsrModelAndServerTestDefaultValues();
226
227         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
228         assertThatExceptionOfType(CertificateDecryptionException.class)
229             .isThrownBy(() -> cmpClient.executeKeyUpdateRequest(csrModel, server, ClientTestData.createOldCertificateModelWithWrongPrivateKey()))
230             .withMessageContaining("Cannot convert certificate or key");
231
232     }
233
234
235     @Test
236     void shouldThrowCmpClientExceptionWhenCannotParseOldCertificate() {
237         setCsrModelAndServerTestDefaultValues();
238
239         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
240
241         // When // Then
242         assertThatExceptionOfType(CertificateDecryptionException.class)
243             .isThrownBy(() -> cmpClient.executeKeyUpdateRequest(csrModel, server, ClientTestData.createOldCertificateModelWithWrongCert()))
244             .withMessageContaining("Incorrect certificate, decryption failed");
245     }
246
247
248     @Test
249     void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr()
250             throws Exception {
251         // given
252         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
253         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
254         setCsrModelAndServerValues(
255                 "mypassword",
256                 "senderKID",
257                 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
258                 beforeDate,
259                 afterDate);
260         when(httpClient.execute(any())).thenReturn(httpResponse);
261         when(httpResponse.getEntity()).thenReturn(httpEntity);
262
263         try (final InputStream is =
264                      this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
265              BufferedInputStream bis = new BufferedInputStream(is)) {
266
267             byte[] ba = IOUtils.toByteArray(bis);
268             doAnswer(
269                     invocation -> {
270                         OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
271                         os.write(ba);
272                         return null;
273                     })
274                     .when(httpEntity)
275                     .writeTo(any(OutputStream.class));
276         }
277         CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
278         // when
279         Cmpv2CertificationModel cmpClientResult =
280                 cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter);
281         // then
282         assertNotNull(cmpClientResult);
283     }
284
285     @Test
286     void
287     shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse()
288             throws Exception {
289         // given
290         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
291         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
292         setCsrModelAndServerValues(
293                 "password",
294                 "senderKID",
295                 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
296                 beforeDate,
297                 afterDate);
298         when(httpClient.execute(any())).thenReturn(httpResponse);
299         when(httpResponse.getEntity()).thenReturn(httpEntity);
300
301         try (final InputStream is =
302                      this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
303              BufferedInputStream bis = new BufferedInputStream(is)) {
304
305             byte[] ba = IOUtils.toByteArray(bis);
306             doAnswer(
307                     invocation -> {
308                         OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
309                         os.write(ba);
310                         return null;
311                     })
312                     .when(httpEntity)
313                     .writeTo(any(OutputStream.class));
314         }
315         CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
316         // then
317         Assertions.assertThrows(
318                 CmpClientException.class,
319                 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
320     }
321
322     @Test
323     void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
324             throws Exception {
325         // given
326         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
327         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
328         setCsrModelAndServerValues(
329                 "password",
330                 "senderKID",
331                 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
332                 beforeDate,
333                 afterDate);
334         when(httpClient.execute(any())).thenReturn(httpResponse);
335         when(httpResponse.getEntity()).thenReturn(httpEntity);
336
337         try (final InputStream is =
338                      this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword");
339              BufferedInputStream bis = new BufferedInputStream(is)) {
340
341             byte[] ba = IOUtils.toByteArray(bis);
342             doAnswer(
343                     invocation -> {
344                         OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
345                         os.write(ba);
346                         return null;
347                     })
348                     .when(httpEntity)
349                     .writeTo(any(OutputStream.class));
350         }
351         CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
352
353         // then
354         Assertions.assertThrows(
355                 CmpServerException.class,
356                 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
357     }
358
359
360     @Test
361     void shouldThrowExceptionWhenResponseNotContainProtectionAlgorithmField()
362         throws IOException, ParseException {
363
364         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
365         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
366         setCsrModelAndServerValues(
367             "password",
368             "senderKID",
369             "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
370             beforeDate,
371             afterDate);
372
373         when(httpClient.execute(any())).thenReturn(httpResponse);
374         when(httpResponse.getEntity()).thenReturn(httpEntity);
375
376         try (
377             BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(
378                 preparePkiMessageWithoutProtectionAlgorithm().getEncoded()
379             ))) {
380
381             byte[] ba = IOUtils.toByteArray(bis);
382             doAnswer(
383                 invocation -> {
384                     OutputStream os = invocation.getArgument(0);
385                     os.write(ba);
386                     return null;
387                 })
388                 .when(httpEntity)
389                 .writeTo(any(OutputStream.class));
390         }
391
392         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
393
394         assertThatExceptionOfType(CmpClientException.class)
395             .isThrownBy(() -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter))
396             .withMessageContaining("CMP response does not contain Protection Algorithm field");
397
398     }
399
400     @Test
401     void shouldThrowIllegalArgumentExceptionWhencreateCertificateCalledWithInvalidCsr()
402             throws ParseException {
403         // given
404         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
405         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
406         setCsrModelAndServerValues(
407                 "password",
408                 "senderKID",
409                 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
410                 beforeDate,
411                 afterDate);
412         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
413         // then
414         Assertions.assertThrows(
415                 IllegalArgumentException.class,
416                 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
417     }
418
419     @Test
420     void shouldThrowIoExceptionWhenCreateCertificateCalledWithNoServerAvailable()
421             throws IOException, ParseException {
422         // given
423         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
424         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
425         setCsrModelAndServerValues(
426                 "myPassword",
427                 "sender",
428                 "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest",
429                 beforeDate,
430                 afterDate);
431         when(httpClient.execute(any())).thenThrow(IOException.class);
432         CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
433         // then
434         Assertions.assertThrows(
435                 CmpClientException.class,
436                 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
437     }
438
439     private void mockCorrectKeyUpdateResponse() throws IOException {
440         doAnswer(
441             invocation -> {
442                 OutputStream os = invocation.getArgument(0);
443                 os.write(BASE64_DECODER.decode(ClientTestData.KUR_CORRECT_SERVER_RESPONSE_ENCODED.getBytes()));
444                 return null;
445             })
446             .when(httpEntity)
447             .writeTo(any(OutputStream.class));
448     }
449
450     private void setCsrModelAndServerValues(String iak, String rv, String externalCaUrl, Date notBefore, Date notAfter) {
451         csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
452
453         Authentication authentication = new Authentication();
454         authentication.setIak(iak);
455         authentication.setRv(rv);
456         server = new Cmpv2Server();
457         server.setAuthentication(authentication);
458         server.setUrl(externalCaUrl);
459         server.setIssuerDN(dn);
460         this.notBefore = notBefore;
461         this.notAfter = notAfter;
462     }
463
464     private void setCsrModelAndServerTestDefaultValues() {
465         csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
466
467         Authentication authentication = new Authentication();
468         authentication.setIak("mypassword");
469         authentication.setRv("senderKID");
470         server = new Cmpv2Server();
471         server.setAuthentication(authentication);
472         server.setUrl("http://127.0.0.1/ejbca/publicweb/cmp/cmp");
473         server.setIssuerDN(dn);
474     }
475
476     private PKIMessage preparePkiMessageWithoutProtectionAlgorithm() {
477
478         CertTemplateBuilder certTemplateBuilder = new CertTemplateBuilder();
479         X500Name issuerDN = getTestIssuerDN();
480
481         certTemplateBuilder.setIssuer(issuerDN);
482         certTemplateBuilder.setSerialNumber(new ASN1Integer(0L));
483
484         CertRequest certRequest = new CertRequest(4, certTemplateBuilder.build(), null);
485         CertReqMsg certReqMsg = new CertReqMsg(certRequest, new ProofOfPossession(), null);
486         CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
487
488         PKIHeaderBuilder pkiHeaderBuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, new GeneralName(issuerDN), new GeneralName(issuerDN));
489         pkiHeaderBuilder.setMessageTime(new ASN1GeneralizedTime(new Date()));
490         pkiHeaderBuilder.setProtectionAlg(null);
491
492         PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages);
493         return new PKIMessage(pkiHeaderBuilder.build(), pkiBody, new DERBitString("test".getBytes()));
494     }
495
496     private X500Name getTestIssuerDN() {
497         return new X500NameBuilder()
498             .addRDN(BCStyle.O, "Test_Organization")
499             .addRDN(BCStyle.UID, "Test_UID")
500             .addRDN(BCStyle.CN, "Test_CA")
501             .build();
502     }
503
504     private static Stream<Arguments> getTestUpdateModelWithSupportedPrivateKeys()
505         throws CertificateDecryptionException {
506         return Stream.of(
507             Arguments.of(createOldCertificateModelWithPrivateKeyInPkcs1()),
508             Arguments.of(createOldCertificateModelWithPrivateKeyInPkcs8())
509         );
510     }
511
512 }