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.assertDoesNotThrow;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.Mockito.doAnswer;
26 import static org.mockito.Mockito.spy;
27 import static org.mockito.Mockito.when;
28 import static org.mockito.MockitoAnnotations.initMocks;
29 import static org.onap.oom.certservice.cmpv2client.ClientTestData.createOldCertificateModelWithPrivateKeyInPkcs1;
30 import static org.onap.oom.certservice.cmpv2client.ClientTestData.createOldCertificateModelWithPrivateKeyInPkcs8;
32 import java.io.BufferedInputStream;
33 import java.io.ByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.security.KeyFactory;
39 import java.security.KeyPair;
40 import java.security.NoSuchAlgorithmException;
41 import java.security.NoSuchProviderException;
42 import java.security.PrivateKey;
43 import java.security.PublicKey;
44 import java.security.Security;
46 import java.security.spec.InvalidKeySpecException;
47 import java.security.spec.PKCS8EncodedKeySpec;
48 import java.security.spec.X509EncodedKeySpec;
49 import java.text.ParseException;
50 import java.text.SimpleDateFormat;
51 import java.util.Base64;
52 import java.util.Base64.Decoder;
53 import java.util.Date;
55 import java.util.stream.Stream;
56 import org.apache.commons.io.IOUtils;
57 import org.apache.http.HttpEntity;
58 import org.apache.http.client.methods.CloseableHttpResponse;
59 import org.apache.http.impl.client.CloseableHttpClient;
60 import org.bouncycastle.asn1.ASN1GeneralizedTime;
61 import org.bouncycastle.asn1.ASN1Integer;
62 import org.bouncycastle.asn1.DERBitString;
63 import org.bouncycastle.asn1.cmp.PKIBody;
64 import org.bouncycastle.asn1.cmp.PKIHeader;
65 import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
66 import org.bouncycastle.asn1.cmp.PKIMessage;
67 import org.bouncycastle.asn1.crmf.CertReqMessages;
68 import org.bouncycastle.asn1.crmf.CertReqMsg;
69 import org.bouncycastle.asn1.crmf.CertRequest;
70 import org.bouncycastle.asn1.crmf.CertTemplateBuilder;
71 import org.bouncycastle.asn1.crmf.ProofOfPossession;
72 import org.bouncycastle.asn1.x500.X500Name;
73 import org.bouncycastle.asn1.x500.X500NameBuilder;
74 import org.bouncycastle.asn1.x500.style.BCStyle;
75 import org.bouncycastle.asn1.x509.GeneralName;
76 import org.bouncycastle.jce.provider.BouncyCastleProvider;
77 import org.junit.jupiter.api.Assertions;
78 import org.junit.jupiter.api.BeforeEach;
79 import org.junit.jupiter.api.Test;
80 import org.junit.jupiter.params.ParameterizedTest;
81 import org.junit.jupiter.params.provider.Arguments;
82 import org.junit.jupiter.params.provider.MethodSource;
83 import org.mockito.Mock;
84 import org.onap.oom.certservice.certification.configuration.model.Authentication;
85 import org.onap.oom.certservice.certification.configuration.model.Cmpv2Server;
86 import org.onap.oom.certservice.certification.exception.CertificateDecryptionException;
87 import org.onap.oom.certservice.certification.model.CsrModel;
88 import org.onap.oom.certservice.certification.model.OldCertificateModel;
89 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
90 import org.onap.oom.certservice.cmpv2client.exceptions.CmpServerException;
91 import org.onap.oom.certservice.cmpv2client.impl.CmpClientImpl;
92 import org.onap.oom.certservice.cmpv2client.model.Cmpv2CertificationModel;
94 class Cmpv2ClientTest {
97 Security.addProvider(new BouncyCastleProvider());
100 private CsrModel csrModel;
101 private Cmpv2Server server;
102 private Date notBefore;
103 private Date notAfter;
108 CloseableHttpClient httpClient;
111 CloseableHttpResponse httpResponse;
114 HttpEntity httpEntity;
116 private static KeyPair keyPair;
118 private static final Decoder BASE64_DECODER = Base64.getDecoder();
122 throws NoSuchProviderException, NoSuchAlgorithmException, IOException,
123 InvalidKeySpecException {
124 keyPair = loadKeyPair();
125 dn = new X500NameBuilder()
126 .addRDN(BCStyle.O, "TestOrganization")
131 public KeyPair loadKeyPair()
132 throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
133 NoSuchProviderException {
135 final InputStream privateInputStream = this.getClass().getResourceAsStream("/privateKey");
136 final InputStream publicInputStream = this.getClass().getResourceAsStream("/publicKey");
137 BufferedInputStream bis = new BufferedInputStream(privateInputStream);
138 byte[] privateBytes = IOUtils.toByteArray(bis);
139 bis = new BufferedInputStream(publicInputStream);
140 byte[] publicBytes = IOUtils.toByteArray(bis);
142 KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME);
143 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicBytes);
144 PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
146 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateBytes);
147 PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
149 return new KeyPair(publicKey, privateKey);
153 void shouldReturnCorrectCmpCertificateForCorrectKeyUpdateResponse() throws CmpClientException, IOException, CertificateDecryptionException {
156 setCsrModelAndServerTestDefaultValues();
157 when(httpClient.execute(any())).thenReturn(httpResponse);
158 when(httpResponse.getEntity()).thenReturn(httpEntity);
160 mockCorrectKeyUpdateResponse();
161 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
164 Cmpv2CertificationModel cmpClientResult =
165 cmpClient.executeKeyUpdateRequest(csrModel, server, ClientTestData.createCorrectOldCertificateModel());
168 assertNotNull(cmpClientResult);
169 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
170 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
175 void shouldReturnCorrectCmpCertificateForCorrectCertificationRequest() throws CmpClientException, IOException {
178 setCsrModelAndServerTestDefaultValues();
179 when(httpClient.execute(any())).thenReturn(httpResponse);
180 when(httpResponse.getEntity()).thenReturn(httpEntity);
184 OutputStream os = invocation.getArgument(0);
185 os.write(BASE64_DECODER.decode(ClientTestData.CR_CORRECT_SERVER_RESPONSE_ENCODED.getBytes()));
189 .writeTo(any(OutputStream.class));
190 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
193 Cmpv2CertificationModel cmpClientResult =
194 cmpClient.executeCertificationRequest(csrModel, server);
197 assertNotNull(cmpClientResult);
198 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
199 assertThat(cmpClientResult.getCertificateChain()).isNotEmpty();
204 @MethodSource("getTestUpdateModelWithSupportedPrivateKeys")
205 void shouldNotThrowExceptionForPrivateKeyInExpectedFormat(OldCertificateModel oldCertificateModel)
209 setCsrModelAndServerTestDefaultValues();
210 when(httpClient.execute(any())).thenReturn(httpResponse);
211 when(httpResponse.getEntity()).thenReturn(httpEntity);
213 mockCorrectKeyUpdateResponse();
214 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
217 assertDoesNotThrow(() -> cmpClient
218 .executeKeyUpdateRequest(csrModel, server, oldCertificateModel)
224 void shouldThrowCmpClientExceptionWhenCannotParseOldPrivateKey() {
225 setCsrModelAndServerTestDefaultValues();
227 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
228 assertThatExceptionOfType(CertificateDecryptionException.class)
229 .isThrownBy(() -> cmpClient.executeKeyUpdateRequest(csrModel, server, ClientTestData.createOldCertificateModelWithWrongPrivateKey()))
230 .withMessageContaining("Cannot convert certificate or key");
236 void shouldThrowCmpClientExceptionWhenCannotParseOldCertificate() {
237 setCsrModelAndServerTestDefaultValues();
239 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
242 assertThatExceptionOfType(CertificateDecryptionException.class)
243 .isThrownBy(() -> cmpClient.executeKeyUpdateRequest(csrModel, server, ClientTestData.createOldCertificateModelWithWrongCert()))
244 .withMessageContaining("Incorrect certificate, decryption failed");
249 void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr()
252 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
253 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
254 setCsrModelAndServerValues(
257 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
260 when(httpClient.execute(any())).thenReturn(httpResponse);
261 when(httpResponse.getEntity()).thenReturn(httpEntity);
263 try (final InputStream is =
264 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
265 BufferedInputStream bis = new BufferedInputStream(is)) {
267 byte[] ba = IOUtils.toByteArray(bis);
270 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
275 .writeTo(any(OutputStream.class));
277 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
279 Cmpv2CertificationModel cmpClientResult =
280 cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter);
282 assertNotNull(cmpClientResult);
287 shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse()
290 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
291 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
292 setCsrModelAndServerValues(
295 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
298 when(httpClient.execute(any())).thenReturn(httpResponse);
299 when(httpResponse.getEntity()).thenReturn(httpEntity);
301 try (final InputStream is =
302 this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile");
303 BufferedInputStream bis = new BufferedInputStream(is)) {
305 byte[] ba = IOUtils.toByteArray(bis);
308 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
313 .writeTo(any(OutputStream.class));
315 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
317 Assertions.assertThrows(
318 CmpClientException.class,
319 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
323 void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword()
326 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
327 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
328 setCsrModelAndServerValues(
331 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
334 when(httpClient.execute(any())).thenReturn(httpResponse);
335 when(httpResponse.getEntity()).thenReturn(httpEntity);
337 try (final InputStream is =
338 this.getClass().getResourceAsStream("/ReturnedFailurePKIMessageBadPassword");
339 BufferedInputStream bis = new BufferedInputStream(is)) {
341 byte[] ba = IOUtils.toByteArray(bis);
344 OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0];
349 .writeTo(any(OutputStream.class));
351 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
354 Assertions.assertThrows(
355 CmpServerException.class,
356 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
361 void shouldThrowExceptionWhenResponseNotContainProtectionAlgorithmField()
362 throws IOException, ParseException {
364 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
365 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
366 setCsrModelAndServerValues(
369 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
373 when(httpClient.execute(any())).thenReturn(httpResponse);
374 when(httpResponse.getEntity()).thenReturn(httpEntity);
377 BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(
378 preparePkiMessageWithoutProtectionAlgorithm().getEncoded()
381 byte[] ba = IOUtils.toByteArray(bis);
384 OutputStream os = invocation.getArgument(0);
389 .writeTo(any(OutputStream.class));
392 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
394 assertThatExceptionOfType(CmpClientException.class)
395 .isThrownBy(() -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter))
396 .withMessageContaining("CMP response does not contain Protection Algorithm field");
401 void shouldThrowIllegalArgumentExceptionWhencreateCertificateCalledWithInvalidCsr()
402 throws ParseException {
404 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
405 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
406 setCsrModelAndServerValues(
409 "http://127.0.0.1/ejbca/publicweb/cmp/cmp",
412 CmpClientImpl cmpClient = new CmpClientImpl(httpClient);
414 Assertions.assertThrows(
415 IllegalArgumentException.class,
416 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
420 void shouldThrowIoExceptionWhenCreateCertificateCalledWithNoServerAvailable()
421 throws IOException, ParseException {
423 Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00");
424 Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00");
425 setCsrModelAndServerValues(
428 "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest",
431 when(httpClient.execute(any())).thenThrow(IOException.class);
432 CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient));
434 Assertions.assertThrows(
435 CmpClientException.class,
436 () -> cmpClient.executeInitializationRequest(csrModel, server, notBefore, notAfter));
439 private void mockCorrectKeyUpdateResponse() throws IOException {
442 OutputStream os = invocation.getArgument(0);
443 os.write(BASE64_DECODER.decode(ClientTestData.KUR_CORRECT_SERVER_RESPONSE_ENCODED.getBytes()));
447 .writeTo(any(OutputStream.class));
450 private void setCsrModelAndServerValues(String iak, String rv, String externalCaUrl, Date notBefore, Date notAfter) {
451 csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
453 Authentication authentication = new Authentication();
454 authentication.setIak(iak);
455 authentication.setRv(rv);
456 server = new Cmpv2Server();
457 server.setAuthentication(authentication);
458 server.setUrl(externalCaUrl);
459 server.setIssuerDN(dn);
460 this.notBefore = notBefore;
461 this.notAfter = notAfter;
464 private void setCsrModelAndServerTestDefaultValues() {
465 csrModel = new CsrModel(null, dn, keyPair.getPrivate(), keyPair.getPublic(), new GeneralName[0]);
467 Authentication authentication = new Authentication();
468 authentication.setIak("mypassword");
469 authentication.setRv("senderKID");
470 server = new Cmpv2Server();
471 server.setAuthentication(authentication);
472 server.setUrl("http://127.0.0.1/ejbca/publicweb/cmp/cmp");
473 server.setIssuerDN(dn);
476 private PKIMessage preparePkiMessageWithoutProtectionAlgorithm() {
478 CertTemplateBuilder certTemplateBuilder = new CertTemplateBuilder();
479 X500Name issuerDN = getTestIssuerDN();
481 certTemplateBuilder.setIssuer(issuerDN);
482 certTemplateBuilder.setSerialNumber(new ASN1Integer(0L));
484 CertRequest certRequest = new CertRequest(4, certTemplateBuilder.build(), null);
485 CertReqMsg certReqMsg = new CertReqMsg(certRequest, new ProofOfPossession(), null);
486 CertReqMessages certReqMessages = new CertReqMessages(certReqMsg);
488 PKIHeaderBuilder pkiHeaderBuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, new GeneralName(issuerDN), new GeneralName(issuerDN));
489 pkiHeaderBuilder.setMessageTime(new ASN1GeneralizedTime(new Date()));
490 pkiHeaderBuilder.setProtectionAlg(null);
492 PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages);
493 return new PKIMessage(pkiHeaderBuilder.build(), pkiBody, new DERBitString("test".getBytes()));
496 private X500Name getTestIssuerDN() {
497 return new X500NameBuilder()
498 .addRDN(BCStyle.O, "Test_Organization")
499 .addRDN(BCStyle.UID, "Test_UID")
500 .addRDN(BCStyle.CN, "Test_CA")
504 private static Stream<Arguments> getTestUpdateModelWithSupportedPrivateKeys()
505 throws CertificateDecryptionException {
507 Arguments.of(createOldCertificateModelWithPrivateKeyInPkcs1()),
508 Arguments.of(createOldCertificateModelWithPrivateKeyInPkcs8())