blob: 12ececd8d4b3a917be4a811e41e718f652a63d21 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.hadoop.hdds.security.x509.certificate.authority;
import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
import org.apache.hadoop.hdds.security.x509.certificate.authority.PKIProfiles.PKIProfile;
import org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
/**
* A base approver class for certificate approvals.
*/
public abstract class BaseApprover implements CertificateApprover {
private static final Logger LOG =
LoggerFactory.getLogger(CertificateApprover.class);
private final PKIProfile profile;
private final SecurityConfig securityConfig;
public BaseApprover(PKIProfile pkiProfile, SecurityConfig config) {
this.profile = Objects.requireNonNull(pkiProfile);
this.securityConfig = Objects.requireNonNull(config);
}
/**
* Returns the Security config.
*
* @return SecurityConfig
*/
public SecurityConfig getSecurityConfig() {
return securityConfig;
}
/**
* Returns the Attribute array that encodes extensions.
*
* @param request - Certificate Request
* @return - An Array of Attributes that encode various extensions requested
* in this certificate.
*/
Attribute[] getAttributes(PKCS10CertificationRequest request) {
Objects.requireNonNull(request);
return
request.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
}
/**
* Returns a list of Extensions encoded in a given attribute.
*
* @param attribute - Attribute to decode.
* @return - List of Extensions.
*/
List<Extensions> getExtensionsList(Attribute attribute) {
Objects.requireNonNull(attribute);
List<Extensions> extensionsList = new ArrayList<>();
for (ASN1Encodable value : attribute.getAttributeValues()) {
if(value != null) {
Extensions extensions = Extensions.getInstance(value);
extensionsList.add(extensions);
}
}
return extensionsList;
}
/**
* Returns the Extension decoded into a Java Collection.
* @param extensions - A set of Extensions in ASN.1.
* @return List of Decoded Extensions.
*/
List<Extension> getIndividualExtension(Extensions extensions) {
Objects.requireNonNull(extensions);
List<Extension> extenList = new ArrayList<>();
for (ASN1ObjectIdentifier id : extensions.getExtensionOIDs()) {
if (id != null) {
Extension ext = extensions.getExtension(id);
if (ext != null) {
extenList.add(ext);
}
}
}
return extenList;
}
/**
* This function verifies all extensions in the certificate.
*
* @param request - CSR
* @return - true if the extensions are acceptable by the profile, false
* otherwise.
*/
boolean verfiyExtensions(PKCS10CertificationRequest request) {
Objects.requireNonNull(request);
/*
* Inside a CSR we have
* 1. A list of Attributes
* 2. Inside each attribute a list of extensions.
* 3. We need to walk thru the each extension and verify they
* are expected and we can put that into a certificate.
*/
for (Attribute attr : getAttributes(request)) {
for (Extensions extensionsList : getExtensionsList(attr)) {
for (Extension extension : getIndividualExtension(extensionsList)) {
if (!profile.validateExtension(extension)) {
LOG.error("Failed to verify extension. {}",
extension.getExtnId().getId());
return false;
}
}
}
}
return true;
}
/**
* Verifies the Signature on the CSR is valid.
*
* @param pkcs10Request - PCKS10 Request.
* @return True if it is valid, false otherwise.
* @throws OperatorCreationException - On Error.
* @throws PKCSException - on Error.
*/
boolean verifyPkcs10Request(PKCS10CertificationRequest pkcs10Request)
throws OperatorCreationException, PKCSException {
ContentVerifierProvider verifierProvider = new
JcaContentVerifierProviderBuilder()
.setProvider(this.securityConfig.getProvider())
.build(pkcs10Request.getSubjectPublicKeyInfo());
return
pkcs10Request.isSignatureValid(verifierProvider);
}
/**
* {@inheritDoc}
*/
@Override
public CompletableFuture<X509CertificateHolder> inspectCSR(String csr)
throws IOException {
return inspectCSR(CertificateSignRequest.getCertificationRequest(csr));
}
/**
* {@inheritDoc}
*/
@Override
public CompletableFuture<X509CertificateHolder>
inspectCSR(PKCS10CertificationRequest csr) {
/**
* The base approver executes the following algorithm to verify that a
* CSR meets the PKI Profile criteria.
*
* 0. For time being (Until we have SCM HA) we will deny all request to
* become an intermediary CA. So we will not need to verify using CA
* profile, right now.
*
* 1. We verify the proof of possession. That is we verify the entity
* that sends us the CSR indeed has the private key for the said public key.
*
* 2. Then we will verify the RDNs meet the format and the Syntax that
* PKI profile dictates.
*
* 3. Then we decode each and every extension and ask if the PKI profile
* approves of these extension requests.
*
* 4. If all of these pass, We will return a Future which will point to
* the Certificate when finished.
*/
CompletableFuture<X509CertificateHolder> response =
new CompletableFuture<>();
try {
// Step 0: Verify this is not a CA Certificate.
// Will be done by the Ozone PKI profile for time being.
// If there are any basicConstraints, they will flagged as not
// supported for time being.
// Step 1: Let us verify that Certificate is indeed signed by someone
// who has access to the private key.
if (!verifyPkcs10Request(csr)) {
LOG.error("Failed to verify the signature in CSR.");
response.completeExceptionally(new SCMSecurityException("Failed to " +
"verify the CSR."));
}
// Step 2: Verify the RDNs are in the correct format.
// TODO: Ozone Profile does not verify RDN now, so this call will pass.
for (RDN rdn : csr.getSubject().getRDNs()) {
if (!profile.validateRDN(rdn)) {
LOG.error("Failed in verifying RDNs");
response.completeExceptionally(new SCMSecurityException("Failed to " +
"verify the RDNs. Please check the subject name."));
}
}
// Step 3: Verify the Extensions.
if (!verfiyExtensions(csr)) {
LOG.error("failed in verification of extensions.");
response.completeExceptionally(new SCMSecurityException("Failed to " +
"verify extensions."));
}
} catch (OperatorCreationException | PKCSException e) {
LOG.error("Approval Failure.", e);
response.completeExceptionally(new SCMSecurityException(e));
}
return response;
}
}