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