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 shouldReturnCorrectCmpCertificateForCorrectCertificationRequest() throws CmpClientException, IOException {
176 setCsrModelAndServerTestDefaultValues();
177 when(httpClient.execute(any())).thenReturn(httpResponse);
178 when(httpResponse.getEntity()).thenReturn(httpEntity);
182 OutputStream os = invocation.getArgument(0);
183 os.write(BASE64_DECODER.decode(ClientTestData.CR_CORRECT_SERVER_RESPONSE_ENCODED.getBytes()));
187 .writeTo(any(OutputStream.class));
188 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
191 Cmpv2CertificationModel cmpClientResult =
192 cmpClient.certificationRequest(csrModel, server);
195 assertNotNull(cmpClientResult);
196 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
197 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
202 void shouldThrowCmpClientExceptionWhenCannotParseOldPrivateKey() {
203 setCsrModelAndServerTestDefaultValues();
205 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
206 assertThatExceptionOfType(CmpClientException.class)
207 .isThrownBy(() -> cmpClient.updateCertificate(csrModel, server, ClientTestData.TEST_CERTIFICATE_UPDATE_MODEL_WITH_WRONG_PRIVATE_KEY))
208 .withMessageContaining("Cannot parse old private key");
214 void shouldThrowCMPClientExceptionWhenCannotParseOldCertificate() {
215 setCsrModelAndServerTestDefaultValues();
217 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
220 assertThatExceptionOfType(CmpClientException.class)
221 .isThrownBy(() -> cmpClient.updateCertificate(csrModel, server, ClientTestData.TEST_CERTIFICATE_UPDATE_MODEL_WITH_WRONG_OLD_CERT))
222 .withMessageContaining("Cannot parse old certificate");
227 void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr()
230 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
231 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
232 setCsrModelAndServerValues(
235 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
238 when(httpClient.execute(any())).thenReturn(httpResponse);
239 when(httpResponse.getEntity()).thenReturn(httpEntity);
241 try (final InputStream is =
242 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
243 BufferedInputStream bis = new BufferedInputStream(is)) {
245 byte[] ba = IOUtils.toByteArray(bis);
248 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
253 .writeTo(any(OutputStream.class));
255 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
257 Cmpv2CertificationModel cmpClientResult =
258 cmpClient.createCertificate(csrModel, server, notBefore, notAfter);
260 assertNotNull(cmpClientResult);
265 shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse()
268 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
269 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
270 setCsrModelAndServerValues(
273 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
276 when(httpClient.execute(any())).thenReturn(httpResponse);
277 when(httpResponse.getEntity()).thenReturn(httpEntity);
279 try (final InputStream is =
280 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
281 BufferedInputStream bis = new BufferedInputStream(is)) {
283 byte[] ba = IOUtils.toByteArray(bis);
286 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
291 .writeTo(any(OutputStream.class));
293 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
295 Assertions.assertThrows(
296 CmpClientException.class,
297 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
301 void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
304 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
305 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
306 setCsrModelAndServerValues(
309 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
312 when(httpClient.execute(any())).thenReturn(httpResponse);
313 when(httpResponse.getEntity()).thenReturn(httpEntity);
315 try (final InputStream is =
316 this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword");
317 BufferedInputStream bis = new BufferedInputStream(is)) {
319 byte[] ba = IOUtils.toByteArray(bis);
322 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
327 .writeTo(any(OutputStream.class));
329 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
332 Assertions.assertThrows(
333 CmpServerException.class,
334 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
339 void shouldThrowExceptionWhenResponseNotContainProtectionAlgorithmField()
340 throws IOException, ParseException {
342 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
343 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
344 setCsrModelAndServerValues(
347 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
351 when(httpClient.execute(any())).thenReturn(httpResponse);
352 when(httpResponse.getEntity()).thenReturn(httpEntity);
355 BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(
356 preparePKIMessageWithoutProtectionAlgorithm().getEncoded()
359 byte[] ba = IOUtils.toByteArray(bis);
362 OutputStream os = invocation.getArgument(0);
367 .writeTo(any(OutputStream.class));
370 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
372 assertThatExceptionOfType(CmpClientException.class)
373 .isThrownBy(() -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter))
374 .withMessageContaining("CMP response does not contain Protection Algorithm field");
379 void shouldThrowIllegalArgumentExceptionWhencreateCertificateCalledWithInvalidCsr()
380 throws ParseException {
382 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
383 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
384 setCsrModelAndServerValues(
387 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
390 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
392 Assertions.assertThrows(
393 IllegalArgumentException.class,
394 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
398 void shouldThrowIoExceptionWhenCreateCertificateCalledWithNoServerAvailable()
399 throws IOException, ParseException {
401 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
402 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
403 setCsrModelAndServerValues(
406 "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest",
409 when(httpClient.execute(any())).thenThrow(IOException.class);
410 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
412 Assertions.assertThrows(
413 CmpClientException.class,
414 () -> cmpClient.createCertificate(csrModel, server, notBefore, notAfter));
417 private void setCsrModelAndServerValues(String iak, String rv, String externalCaUrl, Date notBefore, Date notAfter) {
418 csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
420 Authentication authentication = new Authentication();
421 authentication.setIak(iak);
422 authentication.setRv(rv);
423 server = new Cmpv2Server();
424 server.setAuthentication(authentication);
425 server.setUrl(externalCaUrl);
426 server.setIssuerDN(dn);
427 this.notBefore = notBefore;
428 this.notAfter = notAfter;
431 private void setCsrModelAndServerTestDefaultValues() {
432 csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
434 Authentication authentication = new Authentication();
435 authentication.setIak("mypassword");
436 authentication.setRv("senderKID");
437 server = new Cmpv2Server();
438 server.setAuthentication(authentication);
439 server.setUrl("http://127.0.0.1/ejbca/publicweb/cmp/cmp");
440 server.setIssuerDN(dn);
443 private PKIMessage preparePKIMessageWithoutProtectionAlgorithm() {
445 CertTemplateBuilder certTemplateBuilder = new CertTemplateBuilder();
446 X500Name issuerDN = getTestIssuerDN();
448 certTemplateBuilder.setIssuer(issuerDN);
449 certTemplateBuilder.setSerialNumber(new ASN1Integer(0L));
451 CertRequest certRequest = new CertRequest(4, certTemplateBuilder.build(), null);
452 CertReqMsg certReqMsg = new CertReqMsg(certRequest, new ProofOfPossession(), null);
453 CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
455 PKIHeaderBuilder pkiHeaderBuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, new GeneralName(issuerDN), new GeneralName(issuerDN));
456 pkiHeaderBuilder.setMessageTime(new ASN1GeneralizedTime(new Date()));
457 pkiHeaderBuilder.setProtectionAlg(null);
459 PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages);
460 return new PKIMessage(pkiHeaderBuilder.build(), pkiBody, new DERBitString("test".getBytes()));
463 private X500Name getTestIssuerDN() {
464 return new X500NameBuilder()
465 .addRDN(BCStyle.O, "Test_Organization")
466 .addRDN(BCStyle.UID, "Test_UID")
467 .addRDN(BCStyle.CN, "Test_CA")