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