blob: f26869d18deebb75ba377f69cace1b597292ae0a [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.ozone.security;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
import org.apache.hadoop.security.ssl.TestSSLFactory;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.Time;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test class for {@link OzoneTokenIdentifier}.
*/
public class TestOzoneTokenIdentifier {
private static final Logger LOG = LoggerFactory
.getLogger(TestOzoneTokenIdentifier.class);
private static final String BASEDIR = GenericTestUtils
.getTempPath(TestOzoneTokenIdentifier.class.getSimpleName());
private static final String KEYSTORES_DIR =
new File(BASEDIR).getAbsolutePath();
private static File base;
private static String sslConfsDir;
private static final String EXCLUDE_CIPHERS =
"TLS_ECDHE_RSA_WITH_RC4_128_SHA,"
+ "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, \n"
+ "SSL_RSA_WITH_DES_CBC_SHA,"
+ "SSL_DHE_RSA_WITH_DES_CBC_SHA, "
+ "SSL_RSA_EXPORT_WITH_RC4_40_MD5,\t \n"
+ "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA,"
+ "SSL_RSA_WITH_RC4_128_MD5";
@BeforeClass
public static void setUp() throws Exception {
base = new File(BASEDIR);
FileUtil.fullyDelete(base);
base.mkdirs();
}
private Configuration createConfiguration(boolean clientCert,
boolean trustStore)
throws Exception {
Configuration conf = new Configuration();
KeyStoreTestUtil.setupSSLConfig(KEYSTORES_DIR, sslConfsDir, conf,
clientCert, trustStore, EXCLUDE_CIPHERS);
sslConfsDir = KeyStoreTestUtil.getClasspathDir(TestSSLFactory.class);
return conf;
}
@AfterClass
static public void cleanUp() throws Exception {
FileUtil.fullyDelete(base);
KeyStoreTestUtil.cleanupSSLConfig(KEYSTORES_DIR, sslConfsDir);
}
@Test
public void testSignToken() throws GeneralSecurityException, IOException {
String keystore = new File(KEYSTORES_DIR, "keystore.jks")
.getAbsolutePath();
String truststore = new File(KEYSTORES_DIR, "truststore.jks")
.getAbsolutePath();
String trustPassword = "trustPass";
String keyStorePassword = "keyStorePass";
String keyPassword = "keyPass";
// Create Ozone Master key pair
KeyPair keyPair = KeyStoreTestUtil.generateKeyPair("RSA");
// Create Ozone Master certificate (SCM CA issued cert) and key store
X509Certificate cert = KeyStoreTestUtil
.generateCertificate("CN=OzoneMaster", keyPair, 30, "SHA256withRSA");
KeyStoreTestUtil.createKeyStore(keystore, keyStorePassword, keyPassword,
"OzoneMaster", keyPair.getPrivate(), cert);
// Create trust store and put the certificate in the trust store
Map<String, X509Certificate> certs = Collections.singletonMap("server",
cert);
KeyStoreTestUtil.createTrustStore(truststore, trustPassword, certs);
// Sign the OzoneMaster Token with Ozone Master private key
PrivateKey privateKey = keyPair.getPrivate();
OzoneTokenIdentifier tokenId = new OzoneTokenIdentifier();
tokenId.setOmCertSerialId("123");
byte[] signedToken = signTokenAsymmetric(tokenId, privateKey);
// Verify a valid signed OzoneMaster Token with Ozone Master
// public key(certificate)
boolean isValidToken = verifyTokenAsymmetric(tokenId, signedToken, cert);
LOG.info("{} is {}", tokenId, isValidToken ? "valid." : "invalid.");
// Verify an invalid signed OzoneMaster Token with Ozone Master
// public key(certificate)
tokenId = new OzoneTokenIdentifier(new Text("oozie"),
new Text("rm"), new Text("client"));
tokenId.setOmCertSerialId("123");
LOG.info("Unsigned token {} is {}", tokenId,
verifyTokenAsymmetric(tokenId, RandomUtils.nextBytes(128), cert));
}
public byte[] signTokenAsymmetric(OzoneTokenIdentifier tokenId,
PrivateKey privateKey) throws NoSuchAlgorithmException,
InvalidKeyException, SignatureException {
Signature rsaSignature = Signature.getInstance("SHA256withRSA");
rsaSignature.initSign(privateKey);
rsaSignature.update(tokenId.getBytes());
byte[] signature = rsaSignature.sign();
return signature;
}
public boolean verifyTokenAsymmetric(OzoneTokenIdentifier tokenId,
byte[] signature, Certificate certificate) throws InvalidKeyException,
NoSuchAlgorithmException, SignatureException {
Signature rsaSignature = Signature.getInstance("SHA256withRSA");
rsaSignature.initVerify(certificate);
rsaSignature.update(tokenId.getBytes());
boolean isValide = rsaSignature.verify(signature);
return isValide;
}
private byte[] signTokenSymmetric(OzoneTokenIdentifier identifier,
Mac mac, SecretKey key) {
try {
mac.init(key);
} catch (InvalidKeyException ike) {
throw new IllegalArgumentException("Invalid key to HMAC computation",
ike);
}
return mac.doFinal(identifier.getBytes());
}
OzoneTokenIdentifier generateTestToken() {
OzoneTokenIdentifier tokenIdentifier = new OzoneTokenIdentifier(
new Text(RandomStringUtils.randomAlphabetic(6)),
new Text(RandomStringUtils.randomAlphabetic(5)),
new Text(RandomStringUtils.randomAlphabetic(4)));
tokenIdentifier.setOmCertSerialId("123");
return tokenIdentifier;
}
@Test
public void testAsymmetricTokenPerf() throws NoSuchAlgorithmException,
CertificateEncodingException, NoSuchProviderException,
InvalidKeyException, SignatureException {
final int testTokenCount = 1000;
List<OzoneTokenIdentifier> tokenIds = new ArrayList<>();
List<byte[]> tokenPasswordAsym = new ArrayList<>();
for (int i = 0; i < testTokenCount; i++) {
tokenIds.add(generateTestToken());
}
KeyPair keyPair = KeyStoreTestUtil.generateKeyPair("RSA");
// Create Ozone Master certificate (SCM CA issued cert) and key store
X509Certificate cert;
cert = KeyStoreTestUtil.generateCertificate("CN=OzoneMaster",
keyPair, 30, "SHA256withRSA");
long startTime = Time.monotonicNowNanos();
for (int i = 0; i < testTokenCount; i++) {
tokenPasswordAsym.add(
signTokenAsymmetric(tokenIds.get(i), keyPair.getPrivate()));
}
long duration = Time.monotonicNowNanos() - startTime;
LOG.info("Average token sign time with HmacSha256(RSA/1024 key) is {} ns",
duration/testTokenCount);
startTime = Time.monotonicNowNanos();
for (int i = 0; i < testTokenCount; i++) {
verifyTokenAsymmetric(tokenIds.get(i), tokenPasswordAsym.get(i), cert);
}
duration = Time.monotonicNowNanos() - startTime;
LOG.info("Average token verify time with HmacSha256(RSA/1024 key) "
+ "is {} ns", duration/testTokenCount);
}
@Test
public void testSymmetricTokenPerf() {
String hmacSHA1 = "HmacSHA1";
String hmacSHA256 = "HmacSHA256";
testSymmetricTokenPerfHelper(hmacSHA1, 64);
testSymmetricTokenPerfHelper(hmacSHA256, 1024);
}
public void testSymmetricTokenPerfHelper(String hmacAlgorithm, int keyLen) {
final int testTokenCount = 1000;
List<OzoneTokenIdentifier> tokenIds = new ArrayList<>();
List<byte[]> tokenPasswordSym = new ArrayList<>();
for (int i = 0; i < testTokenCount; i++) {
tokenIds.add(generateTestToken());
}
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance(hmacAlgorithm);
keyGen.init(keyLen);
} catch (NoSuchAlgorithmException nsa) {
throw new IllegalArgumentException("Can't find " + hmacAlgorithm +
" algorithm.");
}
Mac mac;
try {
mac = Mac.getInstance(hmacAlgorithm);
} catch (NoSuchAlgorithmException nsa) {
throw new IllegalArgumentException("Can't find " + hmacAlgorithm +
" algorithm.");
}
SecretKey secretKey = keyGen.generateKey();
long startTime = Time.monotonicNowNanos();
for (int i = 0; i < testTokenCount; i++) {
tokenPasswordSym.add(
signTokenSymmetric(tokenIds.get(i), mac, secretKey));
}
long duration = Time.monotonicNowNanos() - startTime;
LOG.info("Average token sign time with {}({} symmetric key) is {} ns",
hmacAlgorithm, keyLen, duration/testTokenCount);
}
/*
* Test serialization/deserialization of OzoneTokenIdentifier.
*/
@Test
public void testReadWriteInProtobuf() throws IOException {
OzoneTokenIdentifier id = getIdentifierInst();
File idFile = new File(BASEDIR + "/tokenFile");
FileOutputStream fop = new FileOutputStream(idFile);
DataOutputStream dataOutputStream = new DataOutputStream(fop);
id.write(dataOutputStream);
fop.close();
FileInputStream fis = new FileInputStream(idFile);
DataInputStream dis = new DataInputStream(fis);
OzoneTokenIdentifier id2 = new OzoneTokenIdentifier();
id2.readFields(dis);
Assert.assertEquals(id, id2);
}
public OzoneTokenIdentifier getIdentifierInst() {
OzoneTokenIdentifier id = new OzoneTokenIdentifier();
id.setOwner(new Text("User1"));
id.setRenewer(new Text("yarn"));
id.setIssueDate(Time.now());
id.setMaxDate(Time.now() + 5000);
id.setSequenceNumber(1);
id.setOmCertSerialId("123");
return id;
}
}