blob: 28f853a7f63da71fe812dbb8dc9deb61169ce549 [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.certificates.utils;
import com.google.common.base.Preconditions;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException;
import org.apache.hadoop.hdds.security.x509.keys.SecurityUtil;
import org.apache.logging.log4j.util.Strings;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* A certificate sign request object that wraps operations to build a
* PKCS10CertificationRequest to CertificateServer.
*/
public final class CertificateSignRequest {
private final KeyPair keyPair;
private final SecurityConfig config;
private final Extensions extensions;
private String subject;
private String clusterID;
private String scmID;
/**
* Private Ctor for CSR.
*
* @param subject - Subject
* @param scmID - SCM ID
* @param clusterID - Cluster ID
* @param keyPair - KeyPair
* @param config - SCM Config
* @param extensions - CSR extensions
*/
private CertificateSignRequest(String subject, String scmID, String clusterID,
KeyPair keyPair, SecurityConfig config,
Extensions extensions) {
this.subject = subject;
this.clusterID = clusterID;
this.scmID = scmID;
this.keyPair = keyPair;
this.config = config;
this.extensions = extensions;
}
private PKCS10CertificationRequest generateCSR() throws
OperatorCreationException {
X500Name dnName = SecurityUtil.getDistinguishedName(subject, scmID,
clusterID);
PKCS10CertificationRequestBuilder p10Builder =
new JcaPKCS10CertificationRequestBuilder(dnName, keyPair.getPublic());
ContentSigner contentSigner =
new JcaContentSignerBuilder(config.getSignatureAlgo())
.setProvider(config.getProvider())
.build(keyPair.getPrivate());
if (extensions != null) {
p10Builder.addAttribute(
PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions);
}
return p10Builder.build(contentSigner);
}
public static String getEncodedString(PKCS10CertificationRequest request)
throws IOException {
PemObject pemObject =
new PemObject("CERTIFICATE REQUEST", request.getEncoded());
StringWriter str = new StringWriter();
try(JcaPEMWriter pemWriter = new JcaPEMWriter(str)) {
pemWriter.writeObject(pemObject);
}
return str.toString();
}
/**
* Gets a CertificateRequest Object from PEM encoded CSR.
*
* @param csr - PEM Encoded Certificate Request String.
* @return PKCS10CertificationRequest
* @throws IOException - On Error.
*/
public static PKCS10CertificationRequest getCertificationRequest(String csr)
throws IOException {
try (PemReader reader = new PemReader(new StringReader(csr))) {
PemObject pemObject = reader.readPemObject();
if(pemObject.getContent() == null) {
throw new SCMSecurityException("Invalid Certificate signing request");
}
return new PKCS10CertificationRequest(pemObject.getContent());
}
}
/**
* Builder class for Certificate Sign Request.
*/
public static class Builder {
private String subject;
private String clusterID;
private String scmID;
private KeyPair key;
private SecurityConfig config;
private List<GeneralName> altNames;
private Boolean ca = false;
private boolean digitalSignature;
private boolean digitalEncryption;
public CertificateSignRequest.Builder setConfiguration(
Configuration configuration) {
this.config = new SecurityConfig(configuration);
return this;
}
public CertificateSignRequest.Builder setKey(KeyPair keyPair) {
this.key = keyPair;
return this;
}
public CertificateSignRequest.Builder setSubject(String subjectString) {
this.subject = subjectString;
return this;
}
public CertificateSignRequest.Builder setClusterID(String s) {
this.clusterID = s;
return this;
}
public CertificateSignRequest.Builder setScmID(String s) {
this.scmID = s;
return this;
}
public Builder setDigitalSignature(boolean dSign) {
this.digitalSignature = dSign;
return this;
}
public Builder setDigitalEncryption(boolean dEncryption) {
this.digitalEncryption = dEncryption;
return this;
}
// Support SAN extenion with DNS and RFC822 Name
// other name type will be added as needed.
public CertificateSignRequest.Builder addDnsName(String dnsName) {
Preconditions.checkNotNull(dnsName, "dnsName cannot be null");
this.addAltName(GeneralName.dNSName, dnsName);
return this;
}
// IP address is subject to change which is optional for now.
public CertificateSignRequest.Builder addIpAddress(String ip) {
Preconditions.checkNotNull(ip, "Ip address cannot be null");
this.addAltName(GeneralName.iPAddress, ip);
return this;
}
private CertificateSignRequest.Builder addAltName(int tag, String name) {
if (altNames == null) {
altNames = new ArrayList<>();
}
altNames.add(new GeneralName(tag, name));
return this;
}
public CertificateSignRequest.Builder setCA(Boolean isCA) {
this.ca = isCA;
return this;
}
private Extension getKeyUsageExtension() throws IOException {
int keyUsageFlag = KeyUsage.keyAgreement;
if(digitalEncryption){
keyUsageFlag |= KeyUsage.keyEncipherment | KeyUsage.dataEncipherment;
}
if(digitalSignature) {
keyUsageFlag |= KeyUsage.digitalSignature;
}
if (ca) {
keyUsageFlag |= KeyUsage.keyCertSign | KeyUsage.cRLSign;
}
KeyUsage keyUsage = new KeyUsage(keyUsageFlag);
return new Extension(Extension.keyUsage, true,
new DEROctetString(keyUsage));
}
private Optional<Extension> getSubjectAltNameExtension() throws
IOException {
if (altNames != null) {
return Optional.of(new Extension(Extension.subjectAlternativeName,
false, new DEROctetString(new GeneralNames(
altNames.toArray(new GeneralName[altNames.size()])))));
}
return Optional.empty();
}
private Extension getBasicExtension() throws IOException {
// We don't set pathLenConstraint means no limit is imposed.
return new Extension(Extension.basicConstraints,
true, new DEROctetString(new BasicConstraints(ca)));
}
private Extensions createExtensions() throws IOException {
List<Extension> extensions = new ArrayList<>();
// Add basic extension
if(ca) {
extensions.add(getBasicExtension());
}
// Add key usage extension
extensions.add(getKeyUsageExtension());
// Add subject alternate name extension
Optional<Extension> san = getSubjectAltNameExtension();
if (san.isPresent()) {
extensions.add(san.get());
}
return new Extensions(
extensions.toArray(new Extension[extensions.size()]));
}
public PKCS10CertificationRequest build() throws SCMSecurityException {
Preconditions.checkNotNull(key, "KeyPair cannot be null");
Preconditions.checkArgument(Strings.isNotBlank(subject), "Subject " +
"cannot be blank");
try {
CertificateSignRequest csr = new CertificateSignRequest(subject, scmID,
clusterID, key, config, createExtensions());
return csr.generateCSR();
} catch (IOException ioe) {
throw new CertificateException(String.format("Unable to create " +
"extension for certificate sign request for %s.", SecurityUtil
.getDistinguishedName(subject, scmID, clusterID)), ioe.getCause());
} catch (OperatorCreationException ex) {
throw new CertificateException(String.format("Unable to create " +
"certificate sign request for %s.", SecurityUtil
.getDistinguishedName(subject, scmID, clusterID)),
ex.getCause());
}
}
}
}