2 * Copyright (C) 2019 Ericsson Software Technology AB. All rights reserved.
3 * Copyright (C) 2021 Nokia. All rights reserved.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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
18 package org.onap.oom.certservice.cmpv2client;
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;
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;
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;
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;
82 class Cmpv2ClientTest {
85 Security.addProvider(new BouncyCastleProvider());
88 private CsrModel csrModel;
89 private Cmpv2Server server;
90 private Date notBefore;
91 private Date notAfter;
96 CloseableHttpClient httpClient;
99 CloseableHttpResponse httpResponse;
102 HttpEntity httpEntity;
104 private static KeyPair keyPair;
108 throws NoSuchProviderException, NoSuchAlgorithmException, IOException,
109 InvalidKeySpecException {
110 keyPair = loadKeyPair();
111 dn = new X500NameBuilder()
112 .addRDN(BCStyle.O, "TestOrganization")
117 public KeyPair loadKeyPair()
118 throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
119 NoSuchProviderException {
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);
128 KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
129 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicBytes);
130 PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
132 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateBytes);
133 PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
135 return new KeyPair(publicKey, privateKey);
139 void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr()
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(
147 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
150 when(httpClient.execute(any())).thenReturn(httpResponse);
151 when(httpResponse.getEntity()).thenReturn(httpEntity);
153 try (final InputStream is =
154 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
155 BufferedInputStream bis = new BufferedInputStream(is)) {
157 byte[] ba = IOUtils.toByteArray(bis);
160 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
165 .writeTo(any(OutputStream.class));
167 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
169 Cmpv2CertificationModel cmpClientResult =
170 cmpClient.createCertificate(csrModel, server, notBefore, notAfter);
172 assertNotNull(cmpClientResult);
177 shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse()
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(
185 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
188 when(httpClient.execute(any())).thenReturn(httpResponse);
189 when(httpResponse.getEntity()).thenReturn(httpEntity);
191 try (final InputStream is =
192 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
193 BufferedInputStream bis = new BufferedInputStream(is)) {
195 byte[] ba = IOUtils.toByteArray(bis);
198 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
203 .writeTo(any(OutputStream.class));
205 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
207 Assertions.assertThrows(
208 CmpClientException.class,
209 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
213 void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
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(
221 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
224 when(httpClient.execute(any())).thenReturn(httpResponse);
225 when(httpResponse.getEntity()).thenReturn(httpEntity);
227 try (final InputStream is =
228 this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword");
229 BufferedInputStream bis = new BufferedInputStream(is)) {
231 byte[] ba = IOUtils.toByteArray(bis);
234 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
239 .writeTo(any(OutputStream.class));
241 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
244 Assertions.assertThrows(
245 CmpServerException.class,
246 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
251 void shouldThrowExceptionWhenResponseNotContainProtectionAlgorithmField()
252 throws IOException, ParseException {
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(
259 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
263 when(httpClient.execute(any())).thenReturn(httpResponse);
264 when(httpResponse.getEntity()).thenReturn(httpEntity);
267 BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(
268 preparePKIMessageWithoutProtectionAlgorithm().getEncoded()
271 byte[] ba = IOUtils.toByteArray(bis);
274 OutputStream os = invocation.getArgument(0);
279 .writeTo(any(OutputStream.class));
282 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
284 assertThatExceptionOfType(CmpClientException.class)
285 .isThrownBy(() -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter))
286 .withMessageContaining("CMP response does not contain Protection Algorithm field");
291 void shouldThrowIllegalArgumentExceptionWhencreateCertificateCalledWithInvalidCsr()
292 throws ParseException {
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(
299 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
302 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
304 Assertions.assertThrows(
305 IllegalArgumentException.class,
306 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
310 void shouldThrowIoExceptionWhenCreateCertificateCalledWithNoServerAvailable()
311 throws IOException, ParseException {
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(
318 "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest",
321 when(httpClient.execute(any())).thenThrow(IOException.class);
322 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
324 Assertions.assertThrows(
325 CmpClientException.class,
326 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
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]);
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;
343 private PKIMessage preparePKIMessageWithoutProtectionAlgorithm() {
345 CertTemplateBuilder certTemplateBuilder = new CertTemplateBuilder();
346 X500Name issuerDN = getTestIssuerDN();
348 certTemplateBuilder.setIssuer(issuerDN);
349 certTemplateBuilder.setSerialNumber(new ASN1Integer(0L));
351 CertRequest certRequest = new CertRequest(4, certTemplateBuilder.build(), null);
352 CertReqMsg certReqMsg = new CertReqMsg(certRequest, new ProofOfPossession(), null);
353 CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
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);
359 PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages);
360 return new PKIMessage(pkiHeaderBuilder.build(), pkiBody, new DERBitString("test".getBytes()));
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")