| /* |
| * 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.conf.OzoneConfiguration; |
| 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.DefaultProfile; |
| import org.apache.hadoop.hdds.security.x509.certificates.utils.CertificateSignRequest; |
| import org.apache.hadoop.hdds.security.x509.keys.HDDSKeyGenerator; |
| import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; |
| import org.bouncycastle.asn1.x500.X500Name; |
| import org.bouncycastle.asn1.x500.X500NameBuilder; |
| import org.bouncycastle.asn1.x500.style.BCStyle; |
| import org.bouncycastle.asn1.x509.ExtendedKeyUsage; |
| import org.bouncycastle.asn1.x509.Extension; |
| import org.bouncycastle.asn1.x509.Extensions; |
| import org.bouncycastle.asn1.x509.ExtensionsGenerator; |
| import org.bouncycastle.asn1.x509.GeneralName; |
| import org.bouncycastle.asn1.x509.GeneralNames; |
| import org.bouncycastle.asn1.x509.KeyPurposeId; |
| 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.PKCSException; |
| import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| |
| import java.io.IOException; |
| import java.security.KeyPair; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| |
| import static org.apache.hadoop.hdds.HddsConfigKeys.OZONE_METADATA_DIRS; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| /** |
| * Tests for the default PKI Profile. |
| */ |
| public class TestDefaultProfile { |
| @Rule |
| public TemporaryFolder temporaryFolder = new TemporaryFolder(); |
| |
| private OzoneConfiguration configuration; |
| private SecurityConfig securityConfig; |
| private DefaultProfile defaultProfile; |
| private MockApprover testApprover; |
| private KeyPair keyPair; |
| |
| @Before |
| public void setUp() throws Exception { |
| configuration = new OzoneConfiguration(); |
| configuration.set(OZONE_METADATA_DIRS, |
| temporaryFolder.newFolder().toString()); |
| securityConfig = new SecurityConfig(configuration); |
| defaultProfile = new DefaultProfile(); |
| testApprover = new MockApprover(defaultProfile, |
| securityConfig); |
| keyPair = new HDDSKeyGenerator(securityConfig).generateKey(); |
| } |
| |
| /** |
| * Tests the General Names that we support. The default profile supports only |
| * two names right now. |
| */ |
| @Test |
| public void testisSupportedGeneralName() { |
| // Positive tests |
| assertTrue(defaultProfile.isSupportedGeneralName(GeneralName.iPAddress)); |
| assertTrue(defaultProfile.isSupportedGeneralName(GeneralName.dNSName)); |
| assertTrue(defaultProfile.isSupportedGeneralName(GeneralName.otherName)); |
| // Negative Tests |
| assertFalse(defaultProfile.isSupportedGeneralName( |
| GeneralName.directoryName)); |
| assertFalse(defaultProfile.isSupportedGeneralName(GeneralName.rfc822Name)); |
| } |
| |
| /** |
| * Test valid keys are validated correctly. |
| * |
| * @throws SCMSecurityException - on Error. |
| * @throws PKCSException - on Error. |
| * @throws OperatorCreationException - on Error. |
| */ |
| @Test |
| public void testVerifyCertificate() throws SCMSecurityException, |
| PKCSException, OperatorCreationException { |
| PKCS10CertificationRequest csr = new CertificateSignRequest.Builder() |
| .addDnsName("hadoop.apache.org") |
| .addIpAddress("8.8.8.8") |
| .addServiceName("OzoneMarketingCluster001") |
| .setCA(false) |
| .setClusterID("ClusterID") |
| .setScmID("SCMID") |
| .setSubject("Ozone Cluster") |
| .setConfiguration(configuration) |
| .setKey(keyPair) |
| .build(); |
| assertTrue(testApprover.verifyPkcs10Request(csr)); |
| } |
| |
| |
| |
| |
| /** |
| * Test invalid keys fail in the validation. |
| * |
| * @throws SCMSecurityException - on Error. |
| * @throws PKCSException - on Error. |
| * @throws OperatorCreationException - on Error. |
| * @throws NoSuchProviderException - on Error. |
| * @throws NoSuchAlgorithmException - on Error. |
| */ |
| @Test |
| public void testVerifyCertificateInvalidKeys() throws SCMSecurityException, |
| PKCSException, OperatorCreationException, |
| NoSuchProviderException, NoSuchAlgorithmException { |
| KeyPair newKeyPair = new HDDSKeyGenerator(securityConfig).generateKey(); |
| KeyPair wrongKey = new KeyPair(keyPair.getPublic(), |
| newKeyPair.getPrivate()); |
| PKCS10CertificationRequest csr = new CertificateSignRequest.Builder() |
| .addDnsName("hadoop.apache.org") |
| .addIpAddress("8.8.8.8") |
| .setCA(false) |
| .setClusterID("ClusterID") |
| .setScmID("SCMID") |
| .setSubject("Ozone Cluster") |
| .setConfiguration(configuration) |
| .setKey(wrongKey) |
| .build(); |
| // Signature verification should fail here, since the public/private key |
| // does not match. |
| assertFalse(testApprover.verifyPkcs10Request(csr)); |
| } |
| |
| /** |
| * Tests that normal valid extensions work with the default profile. |
| * |
| * @throws SCMSecurityException - on Error. |
| * @throws PKCSException - on Error. |
| * @throws OperatorCreationException - on Error. |
| */ |
| @Test |
| public void testExtensions() throws SCMSecurityException { |
| PKCS10CertificationRequest csr = new CertificateSignRequest.Builder() |
| .addDnsName("hadoop.apache.org") |
| .addIpAddress("192.10.234.6") |
| .setCA(false) |
| .setClusterID("ClusterID") |
| .setScmID("SCMID") |
| .setSubject("Ozone Cluster") |
| .setConfiguration(configuration) |
| .setKey(keyPair) |
| .build(); |
| assertTrue(testApprover.verfiyExtensions(csr)); |
| } |
| |
| /** |
| * Tests that invalid extensions cause a failure in validation. We will fail |
| * if CA extension is enabled. |
| * |
| * @throws SCMSecurityException - on Error. |
| */ |
| |
| @Test |
| public void testInvalidExtensionsWithCA() throws SCMSecurityException { |
| PKCS10CertificationRequest csr = new CertificateSignRequest.Builder() |
| .addDnsName("hadoop.apache.org") |
| .addIpAddress("192.10.234.6") |
| .setCA(true) |
| .setClusterID("ClusterID") |
| .setScmID("SCMID") |
| .setSubject("Ozone Cluster") |
| .setConfiguration(configuration) |
| .setKey(keyPair) |
| .build(); |
| assertFalse(testApprover.verfiyExtensions(csr)); |
| } |
| |
| /** |
| * Tests that invalid extensions cause a failure in validation. We will fail |
| * if rfc222 type names are added, we also add the extension as both |
| * critical and non-critical fashion to verify that the we catch both cases. |
| * |
| * @throws SCMSecurityException - on Error. |
| */ |
| |
| @Test |
| public void testInvalidExtensionsWithEmail() |
| throws IOException, OperatorCreationException { |
| Extensions emailExtension = getSANExtension(GeneralName.rfc822Name, |
| "bilbo@apache.org", false); |
| PKCS10CertificationRequest csr = getInvalidCSR(keyPair, emailExtension); |
| assertFalse(testApprover.verfiyExtensions(csr)); |
| |
| emailExtension = getSANExtension(GeneralName.rfc822Name, "bilbo" + |
| "@apache.org", true); |
| csr = getInvalidCSR(keyPair, emailExtension); |
| assertFalse(testApprover.verfiyExtensions(csr)); |
| |
| } |
| |
| /** |
| * Same test for URI. |
| * @throws IOException - On Error. |
| * @throws OperatorCreationException- on Error. |
| */ |
| @Test |
| public void testInvalidExtensionsWithURI() throws IOException, |
| OperatorCreationException { |
| Extensions oExtension = getSANExtension( |
| GeneralName.uniformResourceIdentifier, "s3g.ozone.org", false); |
| PKCS10CertificationRequest csr = getInvalidCSR(keyPair, oExtension); |
| assertFalse(testApprover.verfiyExtensions(csr)); |
| oExtension = getSANExtension(GeneralName.uniformResourceIdentifier, |
| "s3g.ozone.org", false); |
| csr = getInvalidCSR(keyPair, oExtension); |
| assertFalse(testApprover.verfiyExtensions(csr)); |
| } |
| |
| /** |
| * Assert that if DNS is marked critical our PKI profile will reject it. |
| * @throws IOException - on Error. |
| * @throws OperatorCreationException - on Error. |
| */ |
| @Test |
| public void testInvalidExtensionsWithCriticalDNS() throws IOException, |
| OperatorCreationException { |
| Extensions dnsExtension = getSANExtension(GeneralName.dNSName, |
| "ozone.hadoop.org", |
| true); |
| PKCS10CertificationRequest csr = getInvalidCSR(keyPair, dnsExtension); |
| assertFalse(testApprover.verfiyExtensions(csr)); |
| // This tests should pass, hence the assertTrue |
| dnsExtension = getSANExtension(GeneralName.dNSName, |
| "ozone.hadoop.org", |
| false); |
| csr = getInvalidCSR(keyPair, dnsExtension); |
| assertTrue(testApprover.verfiyExtensions(csr)); |
| } |
| |
| |
| /** |
| * Verify that valid Extended Key usage works as expected. |
| * @throws IOException - on Error. |
| * @throws OperatorCreationException - on Error. |
| */ |
| @Test |
| public void testValidExtendedKeyUsage() throws IOException, |
| OperatorCreationException { |
| Extensions extendedExtension = |
| getKeyUsageExtension(KeyPurposeId.id_kp_clientAuth, false); |
| PKCS10CertificationRequest csr = getInvalidCSR(keyPair, extendedExtension); |
| assertTrue(testApprover.verfiyExtensions(csr)); |
| |
| extendedExtension = getKeyUsageExtension(KeyPurposeId.id_kp_serverAuth, |
| false); |
| csr = getInvalidCSR(keyPair, extendedExtension); |
| assertTrue(testApprover.verfiyExtensions(csr)); |
| } |
| |
| |
| /** |
| * Verify that Invalid Extended Key usage works as expected, that is rejected. |
| * @throws IOException - on Error. |
| * @throws OperatorCreationException - on Error. |
| */ |
| @Test |
| public void testInValidExtendedKeyUsage() throws IOException, |
| OperatorCreationException { |
| Extensions extendedExtension = |
| getKeyUsageExtension(KeyPurposeId.id_kp_clientAuth, true); |
| PKCS10CertificationRequest csr = getInvalidCSR(keyPair, extendedExtension); |
| assertFalse(testApprover.verfiyExtensions(csr)); |
| |
| extendedExtension = getKeyUsageExtension(KeyPurposeId.id_kp_OCSPSigning, |
| false); |
| csr = getInvalidCSR(keyPair, extendedExtension); |
| assertFalse(testApprover.verfiyExtensions(csr)); |
| } |
| |
| |
| |
| /** |
| * Generates an CSR with the extension specified. |
| * This function is used to get an Invalid CSR and test that PKI profile |
| * rejects these invalid extensions, Hence the function name, by itself it |
| * is a well formed CSR, but our PKI profile will treat it as invalid CSR. |
| * |
| * @param kPair - Key Pair. |
| * @return CSR - PKCS10CertificationRequest |
| * @throws OperatorCreationException - on Error. |
| */ |
| private PKCS10CertificationRequest getInvalidCSR(KeyPair kPair, |
| Extensions extensions) throws OperatorCreationException { |
| X500NameBuilder namebuilder = |
| new X500NameBuilder(X500Name.getDefaultStyle()); |
| namebuilder.addRDN(BCStyle.CN, "invalidCert"); |
| PKCS10CertificationRequestBuilder p10Builder = |
| new JcaPKCS10CertificationRequestBuilder(namebuilder.build(), |
| keyPair.getPublic()); |
| p10Builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, |
| extensions); |
| JcaContentSignerBuilder csBuilder = |
| new JcaContentSignerBuilder(this.securityConfig.getSignatureAlgo()); |
| ContentSigner signer = csBuilder.build(keyPair.getPrivate()); |
| return p10Builder.build(signer); |
| } |
| |
| /** |
| * Generate an Extension with rfc822Name. |
| * @param extensionCode - Extension Code. |
| * @param value - email to be added to the certificate |
| * @param critical - boolean value that marks the extension as critical. |
| * @return - An Extension list with email address. |
| * @throws IOException |
| */ |
| private Extensions getSANExtension(int extensionCode, String value, |
| boolean critical) throws IOException { |
| GeneralName extn = new GeneralName(extensionCode, |
| value); |
| ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator(); |
| extensionsGenerator.addExtension(Extension.subjectAlternativeName, critical, |
| new GeneralNames(extn)); |
| return extensionsGenerator.generate(); |
| } |
| |
| /** |
| * Returns a extension with Extended Key usage. |
| * @param purposeId - Usage that we want to encode. |
| * @param critical - makes the extension critical. |
| * @return Extensions. |
| */ |
| private Extensions getKeyUsageExtension(KeyPurposeId purposeId, |
| boolean critical) throws IOException { |
| ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage(purposeId); |
| ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator(); |
| extensionsGenerator.addExtension( |
| Extension.extendedKeyUsage, critical, extendedKeyUsage); |
| return extensionsGenerator.generate(); |
| } |
| } |