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.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;
85 class Cmpv2ClientTest {
88 Security.addProvider(new BouncyCastleProvider());
91 private CsrModel csrModel;
92 private Cmpv2Server server;
93 private Date notBefore;
94 private Date notAfter;
99 CloseableHttpClient httpClient;
102 CloseableHttpResponse httpResponse;
105 HttpEntity httpEntity;
107 private static KeyPair keyPair;
109 private final static Decoder BASE64_DECODER = Base64.getDecoder();
113 throws NoSuchProviderException, NoSuchAlgorithmException, IOException,
114 InvalidKeySpecException {
115 keyPair = loadKeyPair();
116 dn = new X500NameBuilder()
117 .addRDN(BCStyle.O, "TestOrganization")
122 public KeyPair loadKeyPair()
123 throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
124 NoSuchProviderException {
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);
133 KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
134 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicBytes);
135 PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
137 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateBytes);
138 PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
140 return new KeyPair(publicKey, privateKey);
144 void shouldReturnCorrectCmpCertificateForCorrectKeyUpdateResponse() throws CmpClientException, IOException {
147 setCsrModelAndServerTestDefaultValues();
148 when(httpClient.execute(any())).thenReturn(httpResponse);
149 when(httpResponse.getEntity()).thenReturn(httpEntity);
153 OutputStream os = invocation.getArgument(0);
154 os.write(BASE64_DECODER.decode(ClientTestData.KUR_CORRECT_SERVER_RESPONSE_ENCODED.getBytes()));
158 .writeTo(any(OutputStream.class));
159 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
162 Cmpv2CertificationModel cmpClientResult =
163 cmpClient.updateCertificate(csrModel, server, ClientTestData.TEST_CERTIFICATE_UPDATE_MODEL);
166 assertNotNull(cmpClientResult);
167 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
168 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
173 void shouldThrowCmpClientExceptionWhenCannotParseOldPrivateKey() {
174 setCsrModelAndServerTestDefaultValues();
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");
185 void shouldThrowCMPClientExceptionWhenCannotParseOldCertificate() {
186 setCsrModelAndServerTestDefaultValues();
188 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
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");
198 void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr()
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(
206 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
209 when(httpClient.execute(any())).thenReturn(httpResponse);
210 when(httpResponse.getEntity()).thenReturn(httpEntity);
212 try (final InputStream is =
213 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
214 BufferedInputStream bis = new BufferedInputStream(is)) {
216 byte[] ba = IOUtils.toByteArray(bis);
219 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
224 .writeTo(any(OutputStream.class));
226 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
228 Cmpv2CertificationModel cmpClientResult =
229 cmpClient.createCertificate(csrModel, server, notBefore, notAfter);
231 assertNotNull(cmpClientResult);
236 shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse()
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(
244 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
247 when(httpClient.execute(any())).thenReturn(httpResponse);
248 when(httpResponse.getEntity()).thenReturn(httpEntity);
250 try (final InputStream is =
251 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
252 BufferedInputStream bis = new BufferedInputStream(is)) {
254 byte[] ba = IOUtils.toByteArray(bis);
257 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
262 .writeTo(any(OutputStream.class));
264 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
266 Assertions.assertThrows(
267 CmpClientException.class,
268 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
272 void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
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(
280 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
283 when(httpClient.execute(any())).thenReturn(httpResponse);
284 when(httpResponse.getEntity()).thenReturn(httpEntity);
286 try (final InputStream is =
287 this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword");
288 BufferedInputStream bis = new BufferedInputStream(is)) {
290 byte[] ba = IOUtils.toByteArray(bis);
293 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
298 .writeTo(any(OutputStream.class));
300 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
303 Assertions.assertThrows(
304 CmpServerException.class,
305 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
310 void shouldThrowExceptionWhenResponseNotContainProtectionAlgorithmField()
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/cmp",
322 when(httpClient.execute(any())).thenReturn(httpResponse);
323 when(httpResponse.getEntity()).thenReturn(httpEntity);
326 BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(
327 preparePKIMessageWithoutProtectionAlgorithm().getEncoded()
330 byte[] ba = IOUtils.toByteArray(bis);
333 OutputStream os = invocation.getArgument(0);
338 .writeTo(any(OutputStream.class));
341 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
343 assertThatExceptionOfType(CmpClientException.class)
344 .isThrownBy(() -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter))
345 .withMessageContaining("CMP response does not contain Protection Algorithm field");
350 void shouldThrowIllegalArgumentExceptionWhencreateCertificateCalledWithInvalidCsr()
351 throws ParseException {
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(
358 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
361 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
363 Assertions.assertThrows(
364 IllegalArgumentException.class,
365 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
369 void shouldThrowIoExceptionWhenCreateCertificateCalledWithNoServerAvailable()
370 throws IOException, ParseException {
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(
377 "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest",
380 when(httpClient.execute(any())).thenThrow(IOException.class);
381 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
383 Assertions.assertThrows(
384 CmpClientException.class,
385 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
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]);
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;
402 private void setCsrModelAndServerTestDefaultValues() {
403 csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
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);
414 private PKIMessage preparePKIMessageWithoutProtectionAlgorithm() {
416 CertTemplateBuilder certTemplateBuilder = new CertTemplateBuilder();
417 X500Name issuerDN = getTestIssuerDN();
419 certTemplateBuilder.setIssuer(issuerDN);
420 certTemplateBuilder.setSerialNumber(new ASN1Integer(0L));
422 CertRequest certRequest = new CertRequest(4, certTemplateBuilder.build(), null);
423 CertReqMsg certReqMsg = new CertReqMsg(certRequest, new ProofOfPossession(), null);
424 CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
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);
430 PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages);
431 return new PKIMessage(pkiHeaderBuilder.build(), pkiBody, new DERBitString("test".getBytes()));
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")