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