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.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;
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;
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;
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;
86 class Cmpv2ClientTest {
89 Security.addProvider(new BouncyCastleProvider());
92 private CsrModel csrModel;
93 private Cmpv2Server server;
94 private Date notBefore;
95 private Date notAfter;
100 CloseableHttpClient httpClient;
103 CloseableHttpResponse httpResponse;
106 HttpEntity httpEntity;
108 private static KeyPair keyPair;
110 private final static Decoder BASE64_DECODER = Base64.getDecoder();
114 throws NoSuchProviderException, NoSuchAlgorithmException, IOException,
115 InvalidKeySpecException {
116 keyPair = loadKeyPair();
117 dn = new X500NameBuilder()
118 .addRDN(BCStyle.O, "TestOrganization")
123 public KeyPair loadKeyPair()
124 throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
125 NoSuchProviderException {
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);
134 KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
135 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicBytes);
136 PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
138 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateBytes);
139 PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
141 return new KeyPair(publicKey, privateKey);
145 void shouldReturnCorrectCmpCertificateForCorrectKeyUpdateResponse() throws CmpClientException, IOException, CertificateDecryptionException {
148 setCsrModelAndServerTestDefaultValues();
149 when(httpClient.execute(any())).thenReturn(httpResponse);
150 when(httpResponse.getEntity()).thenReturn(httpEntity);
154 OutputStream os = invocation.getArgument(0);
155 os.write(BASE64_DECODER.decode(ClientTestData.KUR_CORRECT_SERVER_RESPONSE_ENCODED.getBytes()));
159 .writeTo(any(OutputStream.class));
160 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
163 Cmpv2CertificationModel cmpClientResult =
164 cmpClient.executeKeyUpdateRequest(csrModel, server, ClientTestData.createCorrectOldCertificateModel());
167 assertNotNull(cmpClientResult);
168 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
169 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
174 void shouldReturnCorrectCmpCertificateForCorrectCertificationRequest() throws CmpClientException, IOException {
177 setCsrModelAndServerTestDefaultValues();
178 when(httpClient.execute(any())).thenReturn(httpResponse);
179 when(httpResponse.getEntity()).thenReturn(httpEntity);
183 OutputStream os = invocation.getArgument(0);
184 os.write(BASE64_DECODER.decode(ClientTestData.CR_CORRECT_SERVER_RESPONSE_ENCODED.getBytes()));
188 .writeTo(any(OutputStream.class));
189 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
192 Cmpv2CertificationModel cmpClientResult =
193 cmpClient.executeCertificationRequest(csrModel, server);
196 assertNotNull(cmpClientResult);
197 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
198 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
203 void shouldThrowCmpClientExceptionWhenCannotParseOldPrivateKey() {
204 setCsrModelAndServerTestDefaultValues();
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");
215 void shouldThrowCMPClientExceptionWhenCannotParseOldCertificate() {
216 setCsrModelAndServerTestDefaultValues();
218 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
221 assertThatExceptionOfType(CertificateDecryptionException.class)
222 .isThrownBy(() -> cmpClient.executeKeyUpdateRequest(csrModel, server, ClientTestData.createOldCertificateModelWithWrongCert()))
223 .withMessageContaining("Incorrect certificate, decryption failed");
228 void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr()
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(
236 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
239 when(httpClient.execute(any())).thenReturn(httpResponse);
240 when(httpResponse.getEntity()).thenReturn(httpEntity);
242 try (final InputStream is =
243 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
244 BufferedInputStream bis = new BufferedInputStream(is)) {
246 byte[] ba = IOUtils.toByteArray(bis);
249 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
254 .writeTo(any(OutputStream.class));
256 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
258 Cmpv2CertificationModel cmpClientResult =
259 cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter);
261 assertNotNull(cmpClientResult);
266 shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse()
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(
274 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
277 when(httpClient.execute(any())).thenReturn(httpResponse);
278 when(httpResponse.getEntity()).thenReturn(httpEntity);
280 try (final InputStream is =
281 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
282 BufferedInputStream bis = new BufferedInputStream(is)) {
284 byte[] ba = IOUtils.toByteArray(bis);
287 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
292 .writeTo(any(OutputStream.class));
294 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
296 Assertions.assertThrows(
297 CmpClientException.class,
298 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
302 void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
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(
310 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
313 when(httpClient.execute(any())).thenReturn(httpResponse);
314 when(httpResponse.getEntity()).thenReturn(httpEntity);
316 try (final InputStream is =
317 this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword");
318 BufferedInputStream bis = new BufferedInputStream(is)) {
320 byte[] ba = IOUtils.toByteArray(bis);
323 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
328 .writeTo(any(OutputStream.class));
330 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
333 Assertions.assertThrows(
334 CmpServerException.class,
335 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
340 void shouldThrowExceptionWhenResponseNotContainProtectionAlgorithmField()
341 throws IOException, ParseException {
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(
348 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
352 when(httpClient.execute(any())).thenReturn(httpResponse);
353 when(httpResponse.getEntity()).thenReturn(httpEntity);
356 BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(
357 preparePKIMessageWithoutProtectionAlgorithm().getEncoded()
360 byte[] ba = IOUtils.toByteArray(bis);
363 OutputStream os = invocation.getArgument(0);
368 .writeTo(any(OutputStream.class));
371 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
373 assertThatExceptionOfType(CmpClientException.class)
374 .isThrownBy(() -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter))
375 .withMessageContaining("CMP response does not contain Protection Algorithm field");
380 void shouldThrowIllegalArgumentExceptionWhencreateCertificateCalledWithInvalidCsr()
381 throws ParseException {
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(
388 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
391 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
393 Assertions.assertThrows(
394 IllegalArgumentException.class,
395 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
399 void shouldThrowIoExceptionWhenCreateCertificateCalledWithNoServerAvailable()
400 throws IOException, ParseException {
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(
407 "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest",
410 when(httpClient.execute(any())).thenThrow(IOException.class);
411 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
413 Assertions.assertThrows(
414 CmpClientException.class,
415 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
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]);
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;
432 private void setCsrModelAndServerTestDefaultValues() {
433 csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
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);
444 private PKIMessage preparePKIMessageWithoutProtectionAlgorithm() {
446 CertTemplateBuilder certTemplateBuilder = new CertTemplateBuilder();
447 X500Name issuerDN = getTestIssuerDN();
449 certTemplateBuilder.setIssuer(issuerDN);
450 certTemplateBuilder.setSerialNumber(new ASN1Integer(0L));
452 CertRequest certRequest = new CertRequest(4, certTemplateBuilder.build(), null);
453 CertReqMsg certReqMsg = new CertReqMsg(certRequest, new ProofOfPossession(), null);
454 CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
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);
460 PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages);
461 return new PKIMessage(pkiHeaderBuilder.build(), pkiBody, new DERBitString("test".getBytes()));
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")