[OOM CERT-SERVICE-API] Add support for URI, IP, E-mail in SANs
[oom/platform/cert-service.git] / certService / src / main / java / org / onap / oom / certservice / cmpv2client / impl / CmpClientImpl.java
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2020 Nordix Foundation.
4  * ================================================================================
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
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.oom.certservice.cmpv2client.impl;
22
23 import java.security.KeyPair;
24 import java.security.PublicKey;
25
26 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseHelper.checkIfCmpResponseContainsError;
27 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseHelper.getCertFromByteArray;
28 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseHelper.verifyAndReturnCertChainAndTrustSTore;
29 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseValidationHelper.checkImplicitConfirm;
30 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifyPasswordBasedProtection;
31 import static org.onap.oom.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifySignature;
32
33 import java.io.IOException;
34 import java.security.cert.CertificateParsingException;
35 import java.security.cert.X509Certificate;
36 import java.util.Collections;
37 import java.util.Date;
38 import java.util.Objects;
39 import java.util.Optional;
40
41 import org.apache.http.impl.client.CloseableHttpClient;
42 import org.bouncycastle.asn1.cmp.CMPCertificate;
43 import org.bouncycastle.asn1.cmp.CertRepMessage;
44 import org.bouncycastle.asn1.cmp.CertResponse;
45 import org.bouncycastle.asn1.cmp.PKIBody;
46 import org.bouncycastle.asn1.cmp.PKIHeader;
47 import org.bouncycastle.asn1.cmp.PKIMessage;
48 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
49 import org.onap.oom.certservice.certification.configuration.model.CaMode;
50 import org.onap.oom.certservice.certification.configuration.model.Cmpv2Server;
51 import org.onap.oom.certservice.certification.model.CsrModel;
52 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
53 import org.onap.oom.certservice.cmpv2client.api.CmpClient;
54 import org.onap.oom.certservice.cmpv2client.model.Cmpv2CertificationModel;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * Implementation of the CmpClient Interface conforming to RFC4210 (Certificate Management Protocol
60  * (CMP)) and RFC4211 (Certificate Request Message Format (CRMF)) standards.
61  */
62 public class CmpClientImpl implements CmpClient {
63
64     private static final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class);
65     private final CloseableHttpClient httpClient;
66
67     private static final String DEFAULT_CA_NAME = "Certification Authority";
68     private static final String DEFAULT_PROFILE = CaMode.RA.getProfile();
69
70     public CmpClientImpl(CloseableHttpClient httpClient) {
71         this.httpClient = httpClient;
72     }
73
74     @Override
75     public Cmpv2CertificationModel createCertificate(
76             CsrModel csrModel,
77             Cmpv2Server server,
78             Date notBefore,
79             Date notAfter)
80             throws CmpClientException {
81
82         validate(csrModel, server, httpClient, notBefore, notAfter);
83         KeyPair keyPair = new KeyPair(csrModel.getPublicKey(), csrModel.getPrivateKey());
84
85         final CreateCertRequest certRequest =
86                 CmpMessageBuilder.of(CreateCertRequest::new)
87                         .with(CreateCertRequest::setIssuerDn, server.getIssuerDN())
88                         .with(CreateCertRequest::setSubjectDn, csrModel.getSubjectData())
89                         .with(CreateCertRequest::setSansArray, csrModel.getSans())
90                         .with(CreateCertRequest::setSubjectKeyPair, keyPair)
91                         .with(CreateCertRequest::setNotBefore, notBefore)
92                         .with(CreateCertRequest::setNotAfter, notAfter)
93                         .with(CreateCertRequest::setInitAuthPassword, server.getAuthentication().getIak())
94                         .with(CreateCertRequest::setSenderKid, server.getAuthentication().getRv())
95                         .build();
96
97         final PKIMessage pkiMessage = certRequest.generateCertReq();
98         Cmpv2HttpClient cmpv2HttpClient = new Cmpv2HttpClient(httpClient);
99         return retrieveCertificates(csrModel, server, pkiMessage, cmpv2HttpClient);
100     }
101
102     @Override
103     public Cmpv2CertificationModel createCertificate(CsrModel csrModel, Cmpv2Server server)
104             throws CmpClientException {
105         return createCertificate(csrModel, server, null, null);
106     }
107
108     private void checkCmpResponse(
109             final PKIMessage respPkiMessage, final PublicKey publicKey, final String initAuthPassword)
110             throws CmpClientException {
111         final PKIHeader header = respPkiMessage.getHeader();
112         final AlgorithmIdentifier protectionAlgo = header.getProtectionAlg();
113         verifySignatureWithPublicKey(respPkiMessage, publicKey);
114         verifyProtectionWithProtectionAlgo(respPkiMessage, initAuthPassword, header, protectionAlgo);
115     }
116
117     private void verifySignatureWithPublicKey(PKIMessage respPkiMessage, PublicKey publicKey)
118             throws CmpClientException {
119         if (Objects.nonNull(publicKey)) {
120             LOG.debug("Verifying signature of the response.");
121             verifySignature(respPkiMessage, publicKey);
122         } else {
123             LOG.error("Public Key is not available, therefore cannot verify signature");
124             throw new CmpClientException(
125                     "Public Key is not available, therefore cannot verify signature");
126         }
127     }
128
129     private void verifyProtectionWithProtectionAlgo(
130             PKIMessage respPkiMessage,
131             String initAuthPassword,
132             PKIHeader header,
133             AlgorithmIdentifier protectionAlgo)
134             throws CmpClientException {
135         if (Objects.nonNull(protectionAlgo)) {
136             LOG.debug("Verifying PasswordBased Protection of the Response.");
137             verifyPasswordBasedProtection(respPkiMessage, initAuthPassword, protectionAlgo);
138             checkImplicitConfirm(header);
139         } else {
140             LOG.error(
141                     "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
142             throw new CmpClientException(
143                     "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm");
144         }
145     }
146
147     private Cmpv2CertificationModel checkCmpCertRepMessage(final PKIMessage respPkiMessage)
148             throws CmpClientException {
149         final PKIBody pkiBody = respPkiMessage.getBody();
150         if (Objects.nonNull(pkiBody) && pkiBody.getContent() instanceof CertRepMessage) {
151             final CertRepMessage certRepMessage = (CertRepMessage) pkiBody.getContent();
152             if (Objects.nonNull(certRepMessage)) {
153                 final CertResponse certResponse =
154                         getCertificateResponseContainingNewCertificate(certRepMessage);
155                 try {
156                     return verifyReturnCertChainAndTrustStore(respPkiMessage, certRepMessage, certResponse);
157                 } catch (IOException | CertificateParsingException ex) {
158                     CmpClientException cmpClientException =
159                             new CmpClientException(
160                                     "Exception occurred while retrieving Certificates from response", ex);
161                     LOG.error("Exception occurred while retrieving Certificates from response", ex);
162                     throw cmpClientException;
163                 }
164             } else {
165                 return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
166             }
167         }
168         return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
169     }
170
171     private Cmpv2CertificationModel verifyReturnCertChainAndTrustStore(
172             PKIMessage respPkiMessage, CertRepMessage certRepMessage, CertResponse certResponse)
173             throws CertificateParsingException, CmpClientException, IOException {
174         LOG.info("Verifying certificates returned as part of CertResponse.");
175         final CMPCertificate cmpCertificate =
176                 certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate();
177         final Optional<X509Certificate> leafCertificate =
178                 getCertFromByteArray(cmpCertificate.getEncoded(), X509Certificate.class);
179         if (leafCertificate.isPresent()) {
180             return verifyAndReturnCertChainAndTrustSTore(
181                     respPkiMessage, certRepMessage, leafCertificate.get());
182         }
183         return new Cmpv2CertificationModel(Collections.emptyList(), Collections.emptyList());
184     }
185
186     private CertResponse getCertificateResponseContainingNewCertificate(
187             CertRepMessage certRepMessage) {
188         return certRepMessage.getResponse()[0];
189     }
190
191     /**
192      * Validate inputs for Certificate Creation.
193      *
194      * @param csrModel Certificate Signing Request model. Must not be {@code null}.
195      * @param server   CMPv2 Server. Must not be {@code null}.
196      * @throws IllegalArgumentException if Before Date is set after the After Date.
197      */
198     private static void validate(
199             final CsrModel csrModel,
200             final Cmpv2Server server,
201             final CloseableHttpClient httpClient,
202             final Date notBefore,
203             final Date notAfter) {
204
205         String caName = CmpUtil.isNullOrEmpty(server.getCaName()) ? server.getCaName() : DEFAULT_CA_NAME;
206         String profile = server.getCaMode() != null ? server.getCaMode().getProfile() : DEFAULT_PROFILE;
207         LOG.info(
208                 "Validate before creating Certificate Request for CA :{} in Mode {} ", caName, profile);
209
210         CmpUtil.notNull(csrModel, "CsrModel Instance");
211         CmpUtil.notNull(csrModel.getSubjectData(), "Subject DN");
212         CmpUtil.notNull(csrModel.getPrivateKey(), "Subject private key");
213         CmpUtil.notNull(csrModel.getPublicKey(), "Subject public key");
214         CmpUtil.notNull(server.getIssuerDN(), "Issuer DN");
215         CmpUtil.notNull(server.getUrl(), "External CA URL");
216         CmpUtil.notNull(server.getAuthentication().getIak(), "IAK/RV Password");
217         CmpUtil.notNull(httpClient, "Closeable Http Client");
218
219         if (notBefore != null && notAfter != null && notBefore.compareTo(notAfter) > 0) {
220             throw new IllegalArgumentException("Before Date is set after the After Date");
221         }
222     }
223
224     private Cmpv2CertificationModel retrieveCertificates(
225             CsrModel csrModel, Cmpv2Server server, PKIMessage pkiMessage, Cmpv2HttpClient cmpv2HttpClient)
226             throws CmpClientException {
227         final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, server.getUrl(), server.getCaName());
228         try {
229             final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes);
230             LOG.info("Received response from Server");
231             checkIfCmpResponseContainsError(respPkiMessage);
232             checkCmpResponse(respPkiMessage, csrModel.getPublicKey(), server.getAuthentication().getIak());
233             return checkCmpCertRepMessage(respPkiMessage);
234         } catch (IllegalArgumentException iae) {
235             CmpClientException cmpClientException =
236                     new CmpClientException(
237                             "Error encountered while processing response from CA server ", iae);
238             LOG.error("Error encountered while processing response from CA server ", iae);
239             throw cmpClientException;
240         }
241     }
242 }