blob: d82b02f43c5dcf9cdbcc75c4d870006b7e1a61bf [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.keys;
import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_METADATA_DIR_NAME;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Set;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.security.x509.SecurityConfig;
import org.apache.hadoop.test.LambdaTestUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
/**
* Test class for HDDS pem writer.
*/
public class TestKeyCodec {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private OzoneConfiguration configuration;
private SecurityConfig securityConfig;
private String component;
private HDDSKeyGenerator keyGenerator;
private String prefix;
@Before
public void init() throws IOException {
configuration = new OzoneConfiguration();
prefix = temporaryFolder.newFolder().toString();
configuration.set(HDDS_METADATA_DIR_NAME, prefix);
keyGenerator = new HDDSKeyGenerator(configuration);
securityConfig = new SecurityConfig(configuration);
component = "test_component";
}
/**
* Assert basic things like we are able to create a file, and the names are
* in expected format etc.
*
* @throws NoSuchProviderException - On Error, due to missing Java
* dependencies.
* @throws NoSuchAlgorithmException - On Error, due to missing Java
* dependencies.
* @throws IOException - On I/O failure.
*/
@Test
public void testWriteKey()
throws NoSuchProviderException, NoSuchAlgorithmException,
IOException, InvalidKeySpecException {
KeyPair keys = keyGenerator.generateKey();
KeyCodec pemWriter = new KeyCodec(securityConfig, component);
pemWriter.writeKey(keys);
// Assert that locations have been created.
Path keyLocation = pemWriter.getSecurityConfig().getKeyLocation(component);
Assert.assertTrue(keyLocation.toFile().exists());
// Assert that locations are created in the locations that we specified
// using the Config.
Assert.assertTrue(keyLocation.toString().startsWith(prefix));
Path privateKeyPath = Paths.get(keyLocation.toString(),
pemWriter.getSecurityConfig().getPrivateKeyFileName());
Assert.assertTrue(privateKeyPath.toFile().exists());
Path publicKeyPath = Paths.get(keyLocation.toString(),
pemWriter.getSecurityConfig().getPublicKeyFileName());
Assert.assertTrue(publicKeyPath.toFile().exists());
// Read the private key and test if the expected String in the PEM file
// format exists.
byte[] privateKey = Files.readAllBytes(privateKeyPath);
String privateKeydata = new String(privateKey, StandardCharsets.UTF_8);
Assert.assertTrue(privateKeydata.contains("PRIVATE KEY"));
// Read the public key and test if the expected String in the PEM file
// format exists.
byte[] publicKey = Files.readAllBytes(publicKeyPath);
String publicKeydata = new String(publicKey, StandardCharsets.UTF_8);
Assert.assertTrue(publicKeydata.contains("PUBLIC KEY"));
// Let us decode the PEM file and parse it back into binary.
KeyFactory kf = KeyFactory.getInstance(
pemWriter.getSecurityConfig().getKeyAlgo());
// Replace the PEM Human readable guards.
privateKeydata =
privateKeydata.replace("-----BEGIN PRIVATE KEY-----\n", "");
privateKeydata =
privateKeydata.replace("-----END PRIVATE KEY-----", "");
// Decode the bas64 to binary format and then use an ASN.1 parser to
// parse the binary format.
byte[] keyBytes = Base64.decodeBase64(privateKeydata);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey privateKeyDecoded = kf.generatePrivate(spec);
assertNotNull("Private Key should not be null",
privateKeyDecoded);
// Let us decode the public key and veriy that we can parse it back into
// binary.
publicKeydata =
publicKeydata.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeydata =
publicKeydata.replace("-----END PUBLIC KEY-----", "");
keyBytes = Base64.decodeBase64(publicKeydata);
X509EncodedKeySpec pubKeyspec = new X509EncodedKeySpec(keyBytes);
PublicKey publicKeyDecoded = kf.generatePublic(pubKeyspec);
assertNotNull("Public Key should not be null",
publicKeyDecoded);
// Now let us assert the permissions on the Directories and files are as
// expected.
Set<PosixFilePermission> expectedSet = pemWriter.getPermissionSet();
Set<PosixFilePermission> currentSet =
Files.getPosixFilePermissions(privateKeyPath);
currentSet.removeAll(expectedSet);
Assert.assertEquals(0, currentSet.size());
currentSet =
Files.getPosixFilePermissions(publicKeyPath);
currentSet.removeAll(expectedSet);
Assert.assertEquals(0, currentSet.size());
currentSet =
Files.getPosixFilePermissions(keyLocation);
currentSet.removeAll(expectedSet);
Assert.assertEquals(0, currentSet.size());
}
/**
* Assert key rewrite fails without force option.
*
* @throws IOException - on I/O failure.
*/
@Test
public void testReWriteKey()
throws Exception {
KeyPair kp = keyGenerator.generateKey();
KeyCodec pemWriter = new KeyCodec(securityConfig, component);
SecurityConfig secConfig = pemWriter.getSecurityConfig();
pemWriter.writeKey(kp);
// Assert that rewriting of keys throws exception with valid messages.
LambdaTestUtils
.intercept(IOException.class, "Private Key file already exists.",
() -> pemWriter.writeKey(kp));
FileUtils.deleteQuietly(Paths.get(
secConfig.getKeyLocation(component).toString() + "/" + secConfig
.getPrivateKeyFileName()).toFile());
LambdaTestUtils
.intercept(IOException.class, "Public Key file already exists.",
() -> pemWriter.writeKey(kp));
FileUtils.deleteQuietly(Paths.get(
secConfig.getKeyLocation(component).toString() + "/" + secConfig
.getPublicKeyFileName()).toFile());
// Should succeed now as both public and private key are deleted.
pemWriter.writeKey(kp);
// Should succeed with overwrite flag as true.
pemWriter.writeKey(kp, true);
}
/**
* Assert key rewrite fails in non Posix file system.
*
* @throws IOException - on I/O failure.
*/
@Test
public void testWriteKeyInNonPosixFS()
throws Exception {
KeyPair kp = keyGenerator.generateKey();
KeyCodec pemWriter = new KeyCodec(securityConfig, component);
pemWriter.setIsPosixFileSystem(() -> false);
// Assert key rewrite fails in non Posix file system.
LambdaTestUtils
.intercept(IOException.class, "Unsupported File System for pem file.",
() -> pemWriter.writeKey(kp));
}
@Test
public void testReadWritePublicKeywithoutArgs()
throws NoSuchProviderException, NoSuchAlgorithmException, IOException,
InvalidKeySpecException {
KeyPair kp = keyGenerator.generateKey();
KeyCodec keycodec = new KeyCodec(securityConfig, component);
keycodec.writeKey(kp);
PublicKey pubKey = keycodec.readPublicKey();
assertNotNull(pubKey);
}
}