[OOM-CERT-SERVICE] Add Key Update Request functionality
[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 shouldThrowCmpClientExceptionWhenCannotParseOldPrivateKey() {
174         setCsrModelAndServerTestDefaultValues();
175
176         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
177         assertThatExceptionOfType(CmpClientException.class)
178             .isThrownBy(() -> cmpClient.updateCertificate(csrModel, server, ClientTestData.TEST_CERTIFICATE_UPDATE_MODEL_WITH_WRONG_PRIVATE_KEY))
179             .withMessageContaining("Cannot parse old private key");
180
181     }
182
183
184     @Test
185     void shouldThrowCMPClientExceptionWhenCannotParseOldCertificate() {
186         setCsrModelAndServerTestDefaultValues();
187
188         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
189
190         // When // Then
191         assertThatExceptionOfType(CmpClientException.class)
192             .isThrownBy(() -> cmpClient.updateCertificate(csrModel, server, ClientTestData.TEST_CERTIFICATE_UPDATE_MODEL_WITH_WRONG_OLD_CERT))
193             .withMessageContaining("Cannot parse old certificate");
194     }
195
196
197     @Test
198     void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr()
199             throws Exception {
200         // given
201         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
202         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
203         setCsrModelAndServerValues(
204                 "mypassword",
205                 "senderKID",
206                 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
207                 beforeDate,
208                 afterDate);
209         when(httpClient.execute(any())).thenReturn(httpResponse);
210         when(httpResponse.getEntity()).thenReturn(httpEntity);
211
212         try (final InputStream is =
213                      this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
214              BufferedInputStream bis = new BufferedInputStream(is)) {
215
216             byte[] ba = IOUtils.toByteArray(bis);
217             doAnswer(
218                     invocation -> {
219                         OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
220                         os.write(ba);
221                         return null;
222                     })
223                     .when(httpEntity)
224                     .writeTo(any(OutputStream.class));
225         }
226         CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
227         // when
228         Cmpv2CertificationModel cmpClientResult =
229                 cmpClient.createCertificate(csrModel, server, notBefore, notAfter);
230         // then
231         assertNotNull(cmpClientResult);
232     }
233
234     @Test
235     void
236     shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse()
237             throws Exception {
238         // given
239         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
240         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
241         setCsrModelAndServerValues(
242                 "password",
243                 "senderKID",
244                 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
245                 beforeDate,
246                 afterDate);
247         when(httpClient.execute(any())).thenReturn(httpResponse);
248         when(httpResponse.getEntity()).thenReturn(httpEntity);
249
250         try (final InputStream is =
251                      this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
252              BufferedInputStream bis = new BufferedInputStream(is)) {
253
254             byte[] ba = IOUtils.toByteArray(bis);
255             doAnswer(
256                     invocation -> {
257                         OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
258                         os.write(ba);
259                         return null;
260                     })
261                     .when(httpEntity)
262                     .writeTo(any(OutputStream.class));
263         }
264         CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
265         // then
266         Assertions.assertThrows(
267                 CmpClientException.class,
268                 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
269     }
270
271     @Test
272     void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
273             throws Exception {
274         // given
275         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
276         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
277         setCsrModelAndServerValues(
278                 "password",
279                 "senderKID",
280                 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
281                 beforeDate,
282                 afterDate);
283         when(httpClient.execute(any())).thenReturn(httpResponse);
284         when(httpResponse.getEntity()).thenReturn(httpEntity);
285
286         try (final InputStream is =
287                      this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword");
288              BufferedInputStream bis = new BufferedInputStream(is)) {
289
290             byte[] ba = IOUtils.toByteArray(bis);
291             doAnswer(
292                     invocation -> {
293                         OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
294                         os.write(ba);
295                         return null;
296                     })
297                     .when(httpEntity)
298                     .writeTo(any(OutputStream.class));
299         }
300         CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
301
302         // then
303         Assertions.assertThrows(
304                 CmpServerException.class,
305                 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
306     }
307
308
309     @Test
310     void shouldThrowExceptionWhenResponseNotContainProtectionAlgorithmField()
311         throws IOException, ParseException {
312
313         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
314         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
315         setCsrModelAndServerValues(
316             "password",
317             "senderKID",
318             "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
319             beforeDate,
320             afterDate);
321
322         when(httpClient.execute(any())).thenReturn(httpResponse);
323         when(httpResponse.getEntity()).thenReturn(httpEntity);
324
325         try (
326             BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(
327                 preparePKIMessageWithoutProtectionAlgorithm().getEncoded()
328             ))) {
329
330             byte[] ba = IOUtils.toByteArray(bis);
331             doAnswer(
332                 invocation -> {
333                     OutputStream os = invocation.getArgument(0);
334                     os.write(ba);
335                     return null;
336                 })
337                 .when(httpEntity)
338                 .writeTo(any(OutputStream.class));
339         }
340
341         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
342
343         assertThatExceptionOfType(CmpClientException.class)
344             .isThrownBy(() -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter))
345             .withMessageContaining("CMP response does not contain Protection Algorithm field");
346
347     }
348
349     @Test
350     void shouldThrowIllegalArgumentExceptionWhencreateCertificateCalledWithInvalidCsr()
351             throws ParseException {
352         // given
353         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
354         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
355         setCsrModelAndServerValues(
356                 "password",
357                 "senderKID",
358                 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
359                 beforeDate,
360                 afterDate);
361         CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
362         // then
363         Assertions.assertThrows(
364                 IllegalArgumentException.class,
365                 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
366     }
367
368     @Test
369     void shouldThrowIoExceptionWhenCreateCertificateCalledWithNoServerAvailable()
370             throws IOException, ParseException {
371         // given
372         Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
373         Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
374         setCsrModelAndServerValues(
375                 "myPassword",
376                 "sender",
377                 "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest",
378                 beforeDate,
379                 afterDate);
380         when(httpClient.execute(any())).thenThrow(IOException.class);
381         CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
382         // then
383         Assertions.assertThrows(
384                 CmpClientException.class,
385                 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
386     }
387
388     private void setCsrModelAndServerValues(String iak, String rv, String externalCaUrl, Date notBefore, Date notAfter) {
389         csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
390
391         Authentication authentication = new Authentication();
392         authentication.setIak(iak);
393         authentication.setRv(rv);
394         server = new Cmpv2Server();
395         server.setAuthentication(authentication);
396         server.setUrl(externalCaUrl);
397         server.setIssuerDN(dn);
398         this.notBefore = notBefore;
399         this.notAfter = notAfter;
400     }
401
402     private void setCsrModelAndServerTestDefaultValues() {
403         csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
404
405         Authentication authentication = new Authentication();
406         authentication.setIak("mypassword");
407         authentication.setRv("senderKID");
408         server = new Cmpv2Server();
409         server.setAuthentication(authentication);
410         server.setUrl("http://127.0.0.1/ejbca/publicweb/cmp/cmp");
411         server.setIssuerDN(dn);
412     }
413
414     private PKIMessage preparePKIMessageWithoutProtectionAlgorithm() {
415
416         CertTemplateBuilder certTemplateBuilder = new CertTemplateBuilder();
417         X500Name issuerDN = getTestIssuerDN();
418
419         certTemplateBuilder.setIssuer(issuerDN);
420         certTemplateBuilder.setSerialNumber(new ASN1Integer(0L));
421
422         CertRequest certRequest = new CertRequest(4, certTemplateBuilder.build(), null);
423         CertReqMsg certReqMsg = new CertReqMsg(certRequest, new ProofOfPossession(), null);
424         CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
425
426         PKIHeaderBuilder pkiHeaderBuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, new GeneralName(issuerDN), new GeneralName(issuerDN));
427         pkiHeaderBuilder.setMessageTime(new ASN1GeneralizedTime(new Date()));
428         pkiHeaderBuilder.setProtectionAlg(null);
429
430         PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages);
431         return new PKIMessage(pkiHeaderBuilder.build(), pkiBody, new DERBitString("test".getBytes()));
432     }
433
434     private X500Name getTestIssuerDN() {
435         return new X500NameBuilder()
436             .addRDN(BCStyle.O, "Test_Organization")
437             .addRDN(BCStyle.UID, "Test_UID")
438             .addRDN(BCStyle.CN, "Test_CA")
439             .build();
440     }
441
442 }