You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by xy...@apache.org on 2018/11/22 01:17:41 UTC
[38/50] [abbrv] hadoop git commit: HDDS-836. Add TokenIdentifier
Ozone for delegation token and block token. Contributed by Ajay Kumar.
HDDS-836. Add TokenIdentifier Ozone for delegation token and block token. Contributed by Ajay Kumar.
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/2aad5e65
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/2aad5e65
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/2aad5e65
Branch: refs/heads/HDDS-4
Commit: 2aad5e65eef82fc515a68f78dc3c4a56605ab82d
Parents: 1e52b9d
Author: Xiaoyu Yao <xy...@apache.org>
Authored: Wed Nov 14 14:26:33 2018 -0800
Committer: Xiaoyu Yao <xy...@apache.org>
Committed: Wed Nov 21 12:13:04 2018 -0800
----------------------------------------------------------------------
.../hdds/security/x509/keys/SecurityUtil.java | 59 ++++
hadoop-hdds/common/src/main/proto/hdds.proto | 24 ++
.../security/OzoneBlockTokenIdentifier.java | 178 +++++++++++
.../ozone/security/OzoneBlockTokenSelector.java | 55 ++++
.../security/OzoneDelegationTokenSelector.java | 52 ++++
.../hadoop/ozone/security/OzoneSecretKey.java | 195 ++++++++++++
.../ozone/security/OzoneTokenIdentifier.java | 217 ++++++++++++++
.../hadoop/ozone/security/package-info.java | 21 ++
.../src/main/proto/OzoneManagerProtocol.proto | 20 ++
.../security/TestOzoneBlockTokenIdentifier.java | 255 ++++++++++++++++
.../security/TestOzoneTokenIdentifier.java | 300 +++++++++++++++++++
.../hadoop/ozone/security/package-info.java | 21 ++
12 files changed, 1397 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java
index 2ca8825..6147d3a 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/security/x509/keys/SecurityUtil.java
@@ -18,6 +18,15 @@
*/
package org.apache.hadoop.hdds.security.x509.keys;
+import java.security.KeyFactory;
+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 org.apache.hadoop.hdds.security.x509.SecurityConfig;
import org.apache.hadoop.hdds.security.x509.exceptions.CertificateException;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
@@ -76,4 +85,54 @@ public final class SecurityUtil {
}
throw new CertificateException("No PKCS#9 extension found in CSR");
}
+
+ /*
+ * Returns private key created from encoded key.
+ * @return private key if successful else returns null.
+ */
+ public static PrivateKey getPrivateKey(byte[] encodedKey,
+ SecurityConfig secureConfig) {
+ PrivateKey pvtKey = null;
+ if (encodedKey == null || encodedKey.length == 0) {
+ return null;
+ }
+
+ try {
+ KeyFactory kf = null;
+
+ kf = KeyFactory.getInstance(secureConfig.getKeyAlgo(),
+ secureConfig.getProvider());
+ pvtKey = kf.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
+
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException |
+ NoSuchProviderException e) {
+ return null;
+ }
+ return pvtKey;
+ }
+
+ /*
+ * Returns public key created from encoded key.
+ * @return public key if successful else returns null.
+ */
+ public static PublicKey getPublicKey(byte[] encodedKey,
+ SecurityConfig secureConfig) {
+ PublicKey key = null;
+ if (encodedKey == null || encodedKey.length == 0) {
+ return null;
+ }
+
+ try {
+ KeyFactory kf = null;
+ kf = KeyFactory.getInstance(secureConfig.getKeyAlgo(),
+ secureConfig.getProvider());
+ key = kf.generatePublic(new X509EncodedKeySpec(encodedKey));
+
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException |
+ NoSuchProviderException e) {
+ return null;
+ }
+ return key;
+ }
+
}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-hdds/common/src/main/proto/hdds.proto
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/proto/hdds.proto b/hadoop-hdds/common/src/main/proto/hdds.proto
index c37683a..2592ddf 100644
--- a/hadoop-hdds/common/src/main/proto/hdds.proto
+++ b/hadoop-hdds/common/src/main/proto/hdds.proto
@@ -185,6 +185,30 @@ message ContainerBlockID {
required int64 localID = 2;
}
+
+/**
+ * Information for the Hdds block token.
+ * When adding further fields, make sure they are optional as they would
+ * otherwise not be backwards compatible.
+ */
+message BlockTokenSecretProto {
+ /**
+ * File access permissions mode.
+ */
+ enum AccessModeProto {
+ READ = 1;
+ WRITE = 2;
+ COPY = 3;
+ DELETE = 4;
+ }
+ required string ownerId = 1;
+ required string blockId = 2;
+ required uint64 expiryDate = 3;
+ required string omCertSerialId = 4;
+ repeated AccessModeProto modes = 5;
+
+}
+
message BlockID {
required ContainerBlockID containerBlockID = 1;
optional uint64 blockCommitSequenceId = 2 [default = 0];
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenIdentifier.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenIdentifier.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenIdentifier.java
new file mode 100644
index 0000000..92b6d16
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenIdentifier.java
@@ -0,0 +1,178 @@
+/**
+ * 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 com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.BlockTokenSecretProto;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.BlockTokenSecretProto.AccessModeProto;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos.BlockTokenSecretProto.Builder;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.TokenIdentifier;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.EnumSet;
+
+/**
+ * Block token identifier for Ozone/HDDS. Ozone block access token is similar
+ * to HDFS block access token, which is meant to be lightweight and
+ * short-lived. No need to renew or revoke a block access token. when a
+ * cached block access token expires, the client simply get a new one.
+ * Block access token should be cached only in memory and never write to disk.
+ */
+@InterfaceAudience.Private
+public class OzoneBlockTokenIdentifier extends TokenIdentifier {
+
+ static final Text KIND_NAME = new Text("HDDS_BLOCK_TOKEN");
+ private long expiryDate;
+ private String ownerId;
+ private String blockId;
+ private final EnumSet<AccessModeProto> modes;
+ private final String omCertSerialId;
+
+ public OzoneBlockTokenIdentifier(String ownerId, String blockId,
+ EnumSet<AccessModeProto> modes, long expiryDate, String omCertSerialId) {
+ this.ownerId = ownerId;
+ this.blockId = blockId;
+ this.expiryDate = expiryDate;
+ this.modes = modes == null ? EnumSet.noneOf(AccessModeProto.class) : modes;
+ this.omCertSerialId = omCertSerialId;
+ }
+
+ @Override
+ public UserGroupInformation getUser() {
+ if (this.getOwnerId() == null || "".equals(this.getOwnerId())) {
+ return UserGroupInformation.createRemoteUser(blockId);
+ }
+ return UserGroupInformation.createRemoteUser(ownerId);
+ }
+
+ public long getExpiryDate() {
+ return expiryDate;
+ }
+
+ public String getOwnerId() {
+ return ownerId;
+ }
+
+ public String getBlockId() {
+ return blockId;
+ }
+
+ public EnumSet<AccessModeProto> getAccessModes() {
+ return modes;
+ }
+
+ public String getOmCertSerialId(){
+ return omCertSerialId;
+ }
+
+ @Override
+ public Text getKind() {
+ return KIND_NAME;
+ }
+
+ @Override
+ public String toString() {
+ return "block_token_identifier (expiryDate=" + this.getExpiryDate()
+ + ", ownerId=" + this.getOwnerId()
+ + ", omCertSerialId=" + this.getOmCertSerialId()
+ + ", blockId=" + this.getBlockId() + ", access modes="
+ + this.getAccessModes() + ")";
+ }
+
+ static boolean isEqual(Object a, Object b) {
+ return a == null ? b == null : a.equals(b);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj instanceof OzoneBlockTokenIdentifier) {
+ OzoneBlockTokenIdentifier that = (OzoneBlockTokenIdentifier) obj;
+ return new EqualsBuilder()
+ .append(this.expiryDate, that.expiryDate)
+ .append(this.ownerId, that.ownerId)
+ .append(this.blockId, that.blockId)
+ .append(this.modes, that.modes)
+ .append(this.omCertSerialId, that.omCertSerialId)
+ .build();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(133, 567)
+ .append(this.expiryDate)
+ .append(this.blockId)
+ .append(this.ownerId)
+ .append(this.modes)
+ .append(this.omCertSerialId)
+ .build();
+ }
+
+ @Override
+ public void readFields(DataInput in) throws IOException {
+ final DataInputStream dis = (DataInputStream) in;
+ if (!dis.markSupported()) {
+ throw new IOException("Could not peek first byte.");
+ }
+ readFieldsProtobuf(dis);
+ }
+
+ @VisibleForTesting
+ public static OzoneBlockTokenIdentifier readFieldsProtobuf(DataInput in)
+ throws IOException {
+ BlockTokenSecretProto tokenPtoto =
+ BlockTokenSecretProto.parseFrom((DataInputStream) in);
+ return new OzoneBlockTokenIdentifier(tokenPtoto.getOwnerId(),
+ tokenPtoto.getBlockId(), EnumSet.copyOf(tokenPtoto.getModesList()),
+ tokenPtoto.getExpiryDate(), tokenPtoto.getOmCertSerialId());
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ writeProtobuf(out);
+ }
+
+ @VisibleForTesting
+ void writeProtobuf(DataOutput out) throws IOException {
+ Builder builder = BlockTokenSecretProto.newBuilder()
+ .setBlockId(this.getBlockId())
+ .setOwnerId(this.getOwnerId())
+ .setOmCertSerialId(this.getOmCertSerialId())
+ .setExpiryDate(this.getExpiryDate());
+ // Add access mode allowed
+ for (AccessModeProto mode : this.getAccessModes()) {
+ builder.addModes(AccessModeProto.valueOf(mode.name()));
+ }
+ out.write(builder.build().toByteArray());
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenSelector.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenSelector.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenSelector.java
new file mode 100644
index 0000000..086e19b
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneBlockTokenSelector.java
@@ -0,0 +1,55 @@
+/**
+ * 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 org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
+import org.apache.hadoop.security.token.TokenSelector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+
+/**
+ * A block token selector for Ozone.
+ */
+@InterfaceAudience.Private
+public class OzoneBlockTokenSelector implements
+ TokenSelector<OzoneBlockTokenIdentifier> {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(OzoneBlockTokenSelector.class);
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Token<OzoneBlockTokenIdentifier> selectToken(Text service,
+ Collection<Token<? extends TokenIdentifier>> tokens) {
+ if (service == null) {
+ return null;
+ }
+ for (Token<? extends TokenIdentifier> token : tokens) {
+ if (OzoneBlockTokenIdentifier.KIND_NAME.equals(token.getKind())) {
+ LOG.trace("Getting token for service:{}", service);
+ return (Token<OzoneBlockTokenIdentifier>) token;
+ }
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java
new file mode 100644
index 0000000..0d63c4e
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneDelegationTokenSelector.java
@@ -0,0 +1,52 @@
+/**
+ * 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.util.Collection;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
+import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSelector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A delegation token selector that is specialized for Ozone.
+ */
+@InterfaceAudience.Private
+public class OzoneDelegationTokenSelector
+ extends AbstractDelegationTokenSelector<DelegationTokenIdentifier> {
+
+ public OzoneDelegationTokenSelector() {
+ super(OzoneTokenIdentifier.KIND_NAME);
+ }
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(OzoneDelegationTokenSelector.class);
+
+ @Override
+ public Token selectToken(Text service,
+ Collection<Token<? extends TokenIdentifier>> tokens) {
+ LOG.trace("Getting token for service {}", service);
+ return super.selectToken(service, tokens);
+ }
+
+}
+
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretKey.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretKey.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretKey.java
new file mode 100644
index 0000000..9b2f912
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneSecretKey.java
@@ -0,0 +1,195 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 com.google.common.base.Preconditions;
+import com.google.protobuf.ByteString;
+import java.io.ByteArrayInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.security.x509.SecurityConfig;
+import org.apache.hadoop.hdds.security.x509.keys.SecurityUtil;
+import org.apache.hadoop.io.Writable;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.SecretKeyProto;
+
+/**
+ * Wrapper class for Ozone/Hdds secret keys. Used in delegation tokens and block
+ * tokens.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class OzoneSecretKey implements Writable {
+
+ private int keyId;
+ private long expiryDate;
+ private PrivateKey privateKey;
+ private PublicKey publicKey;
+ private int maxKeyLen;
+ private SecurityConfig securityConfig;
+
+ public OzoneSecretKey(int keyId, long expiryDate, KeyPair keyPair,
+ int maxKeyLen) {
+ Preconditions.checkNotNull(keyId);
+ this.keyId = keyId;
+ this.expiryDate = expiryDate;
+ byte[] encodedKey = keyPair.getPrivate().getEncoded();
+ this.maxKeyLen = maxKeyLen;
+ if (encodedKey.length > maxKeyLen) {
+ throw new RuntimeException("can't create " + encodedKey.length +
+ " byte long DelegationKey.");
+ }
+ this.privateKey = keyPair.getPrivate();
+ this.publicKey = keyPair.getPublic();
+ }
+
+ /*
+ * Create new instance using default signature algorithm and provider.
+ * */
+ public OzoneSecretKey(int keyId, long expiryDate, byte[] pvtKey,
+ byte[] publicKey, int maxKeyLen) {
+ Preconditions.checkNotNull(pvtKey);
+ Preconditions.checkNotNull(publicKey);
+
+ this.securityConfig = new SecurityConfig(new OzoneConfiguration());
+ this.keyId = keyId;
+ this.expiryDate = expiryDate;
+ this.maxKeyLen = maxKeyLen;
+ if (pvtKey.length > maxKeyLen) {
+ throw new RuntimeException("can't create " + pvtKey.length +
+ " byte long DelegationKey. Max allowed length is " + maxKeyLen);
+ }
+ this.privateKey = SecurityUtil.getPrivateKey(pvtKey, securityConfig);
+ this.publicKey = SecurityUtil.getPublicKey(publicKey, securityConfig);
+ }
+
+ public int getKeyId() {
+ return keyId;
+ }
+
+ public long getExpiryDate() {
+ return expiryDate;
+ }
+
+ public PrivateKey getPrivateKey() {
+ return privateKey;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public int getMaxKeyLen() {
+ return maxKeyLen;
+ }
+
+ public byte[] getEncodedPrivateKey() {
+ return privateKey.getEncoded();
+ }
+
+ public byte[] getEncodedPubliceKey() {
+ return publicKey.getEncoded();
+ }
+
+ public void setExpiryDate(long expiryDate) {
+ this.expiryDate = expiryDate;
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ SecretKeyProto token = SecretKeyProto.newBuilder()
+ .setKeyId(getKeyId())
+ .setExpiryDate(getExpiryDate())
+ .setPrivateKeyBytes(ByteString.copyFrom(getEncodedPrivateKey()))
+ .setPublicKeyBytes(ByteString.copyFrom(getEncodedPubliceKey()))
+ .setMaxKeyLen(getMaxKeyLen())
+ .build();
+ out.write(token.toByteArray());
+ }
+
+ @Override
+ public void readFields(DataInput in) throws IOException {
+ SecretKeyProto secretKey = SecretKeyProto.parseFrom((DataInputStream) in);
+ expiryDate = secretKey.getExpiryDate();
+ keyId = secretKey.getKeyId();
+ privateKey = SecurityUtil.getPrivateKey(secretKey.getPrivateKeyBytes()
+ .toByteArray(), securityConfig);
+ publicKey = SecurityUtil.getPublicKey(secretKey.getPublicKeyBytes()
+ .toByteArray(), securityConfig);
+ maxKeyLen = secretKey.getMaxKeyLen();
+ }
+
+ @Override
+ public int hashCode() {
+ HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(537, 963);
+ hashCodeBuilder.append(getExpiryDate())
+ .append(getKeyId())
+ .append(getEncodedPrivateKey())
+ .append(getEncodedPubliceKey());
+
+ return hashCodeBuilder.build();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj instanceof OzoneSecretKey) {
+ OzoneSecretKey that = (OzoneSecretKey) obj;
+ return new EqualsBuilder()
+ .append(this.keyId, that.keyId)
+ .append(this.expiryDate, that.expiryDate)
+ .append(this.privateKey, that.privateKey)
+ .append(this.publicKey, that.publicKey)
+ .build();
+ }
+ return false;
+ }
+
+ /**
+ * Reads protobuf encoded input stream to construct {@link OzoneSecretKey}.
+ */
+ static OzoneSecretKey readProtoBuf(DataInput in) throws IOException {
+ Preconditions.checkNotNull(in);
+ SecretKeyProto key = SecretKeyProto.parseFrom((DataInputStream) in);
+ return new OzoneSecretKey(key.getKeyId(), key.getExpiryDate(),
+ key.getPrivateKeyBytes().toByteArray(),
+ key.getPublicKeyBytes().toByteArray(), key.getMaxKeyLen());
+ }
+
+ /**
+ * Reads protobuf encoded input stream to construct {@link OzoneSecretKey}.
+ */
+ static OzoneSecretKey readProtoBuf(byte[] identifier) throws IOException {
+ Preconditions.checkNotNull(identifier);
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(
+ identifier));
+ return readProtoBuf(in);
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneTokenIdentifier.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneTokenIdentifier.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneTokenIdentifier.java
new file mode 100644
index 0000000..d053465
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/OzoneTokenIdentifier.java
@@ -0,0 +1,217 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.ByteArrayInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMTokenProto;
+import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier;
+import org.apache.hadoop.security.token.Token;
+
+/**
+ * The token identifier for Ozone Master.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
+public class OzoneTokenIdentifier extends
+ AbstractDelegationTokenIdentifier {
+
+ public final static Text KIND_NAME = new Text("OzoneToken");
+
+ /**
+ * Create an empty delegation token identifier.
+ */
+ public OzoneTokenIdentifier() {
+ super();
+ }
+
+ /**
+ * Create a new ozone master delegation token identifier.
+ *
+ * @param owner the effective username of the token owner
+ * @param renewer the username of the renewer
+ * @param realUser the real username of the token owner
+ */
+ public OzoneTokenIdentifier(Text owner, Text renewer, Text realUser) {
+ super(owner, renewer, realUser);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Text getKind() {
+ return KIND_NAME;
+ }
+
+ /**
+ * Default TrivialRenewer.
+ */
+ @InterfaceAudience.Private
+ public static class Renewer extends Token.TrivialRenewer {
+
+ @Override
+ protected Text getKind() {
+ return KIND_NAME;
+ }
+ }
+
+ /**
+ * Overrides default implementation to write using Protobuf.
+ *
+ * @param out output stream
+ * @throws IOException
+ */
+ @Override
+ public void write(DataOutput out) throws IOException {
+ OMTokenProto token = OMTokenProto.newBuilder()
+ .setOwner(getOwner().toString())
+ .setRealUser(getRealUser().toString())
+ .setRenewer(getRenewer().toString())
+ .setIssueDate(getIssueDate())
+ .setMaxDate(getMaxDate())
+ .setSequenceNumber(getSequenceNumber())
+ .setMasterKeyId(getMasterKeyId()).build();
+ out.write(token.toByteArray());
+ }
+
+ /**
+ * Overrides default implementation to read using Protobuf.
+ *
+ * @param in input stream
+ * @throws IOException
+ */
+ @Override
+ public void readFields(DataInput in) throws IOException {
+ OMTokenProto token = OMTokenProto.parseFrom((DataInputStream) in);
+ setOwner(new Text(token.getOwner()));
+ setRealUser(new Text(token.getRealUser()));
+ setRenewer(new Text(token.getRenewer()));
+ setIssueDate(token.getIssueDate());
+ setMaxDate(token.getMaxDate());
+ setSequenceNumber(token.getSequenceNumber());
+ setMasterKeyId(token.getMasterKeyId());
+ }
+
+ /**
+ * Reads protobuf encoded input stream to construct {@link
+ * OzoneTokenIdentifier}.
+ */
+ public static OzoneTokenIdentifier readProtoBuf(DataInput in)
+ throws IOException {
+ OMTokenProto token = OMTokenProto.parseFrom((DataInputStream) in);
+ OzoneTokenIdentifier identifier = new OzoneTokenIdentifier();
+ identifier.setRenewer(new Text(token.getRenewer()));
+ identifier.setOwner(new Text(token.getOwner()));
+ identifier.setRealUser(new Text(token.getRealUser()));
+ identifier.setMaxDate(token.getMaxDate());
+ identifier.setIssueDate(token.getIssueDate());
+ identifier.setSequenceNumber(token.getSequenceNumber());
+ identifier.setMasterKeyId(token.getMasterKeyId());
+ return identifier;
+ }
+
+ /**
+ * Reads protobuf encoded input stream to construct {@link
+ * OzoneTokenIdentifier}.
+ */
+ public static OzoneTokenIdentifier readProtoBuf(byte[] identifier)
+ throws IOException {
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(
+ identifier));
+ return readProtoBuf(in);
+ }
+
+ /**
+ * Creates new instance.
+ */
+ public static OzoneTokenIdentifier newInstance() {
+ return new OzoneTokenIdentifier();
+ }
+
+ /**
+ * Creates new instance.
+ */
+ public static OzoneTokenIdentifier newInstance(Text owner, Text renewer,
+ Text realUser) {
+ return new OzoneTokenIdentifier(owner, renewer, realUser);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof OzoneTokenIdentifier)) {
+ return false;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Class to encapsulate a token's renew date and password.
+ */
+ @InterfaceStability.Evolving
+ public static class TokenInfo {
+
+ private long renewDate;
+ private byte[] password;
+ private String trackingId;
+
+ public TokenInfo(long renewDate, byte[] password) {
+ this(renewDate, password, null);
+ }
+
+ public TokenInfo(long renewDate, byte[] password,
+ String trackingId) {
+ this.renewDate = renewDate;
+ this.password = Arrays.copyOf(password, password.length);
+ this.trackingId = trackingId;
+ }
+
+ /**
+ * returns renew date.
+ */
+ public long getRenewDate() {
+ return renewDate;
+ }
+
+ /**
+ * returns password.
+ */
+ byte[] getPassword() {
+ return password;
+ }
+
+ /**
+ * returns tracking id.
+ */
+ public String getTrackingId() {
+ return trackingId;
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/package-info.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/package-info.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/package-info.java
new file mode 100644
index 0000000..457f891
--- /dev/null
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/package-info.java
@@ -0,0 +1,21 @@
+/**
+ * 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;
+/**
+ * Ozone security related classes.
+ */
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto
----------------------------------------------------------------------
diff --git a/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto b/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto
index 6320cf1..5caebf5 100644
--- a/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto
+++ b/hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto
@@ -311,6 +311,26 @@ message DeleteBucketResponse {
required Status status = 1;
}
+message OMTokenProto {
+ optional uint32 version = 1;
+ optional string owner = 2;
+ optional string renewer = 3;
+ optional string realUser = 4;
+ optional uint64 issueDate = 5;
+ optional uint64 maxDate = 6;
+ optional uint32 sequenceNumber = 7;
+ optional uint32 masterKeyId = 8;
+ optional uint64 expiryDate = 9;
+}
+
+message SecretKeyProto {
+ required uint32 keyId = 1;
+ required uint64 expiryDate = 2;
+ required bytes privateKeyBytes = 3;
+ required bytes publicKeyBytes = 4;
+ required uint32 maxKeyLen = 5;
+}
+
message ListKeysRequest {
required string volumeName = 1;
required string bucketName = 2;
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneBlockTokenIdentifier.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneBlockTokenIdentifier.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneBlockTokenIdentifier.java
new file mode 100644
index 0000000..5909916
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneBlockTokenIdentifier.java
@@ -0,0 +1,255 @@
+/**
+ * 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.File;
+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.EnumSet;
+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.fs.FileUtil;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
+import org.apache.hadoop.test.GenericTestUtils;
+import org.apache.hadoop.util.Time;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test class for OzoneManagerDelegationToken.
+ */
+public class TestOzoneBlockTokenIdentifier {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(TestOzoneBlockTokenIdentifier.class);
+ private static final String BASEDIR = GenericTestUtils
+ .getTempPath(TestOzoneBlockTokenIdentifier.class.getSimpleName());
+ private static final String KEYSTORES_DIR =
+ new File(BASEDIR).getAbsolutePath();
+ private static long expiryTime;
+ private static KeyPair keyPair;
+ private static X509Certificate cert;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ File base = new File(BASEDIR);
+ FileUtil.fullyDelete(base);
+ base.mkdirs();
+ expiryTime = Time.monotonicNow() + 60 * 60 * 24;
+
+ // Create Ozone Master key pair.
+ keyPair = KeyStoreTestUtil.generateKeyPair("RSA");
+ // Create Ozone Master certificate (SCM CA issued cert) and key store.
+ cert = KeyStoreTestUtil
+ .generateCertificate("CN=OzoneMaster", keyPair, 30, "SHA256withRSA");
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ // 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";
+
+
+ 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();
+ OzoneBlockTokenIdentifier tokenId = new OzoneBlockTokenIdentifier(
+ "testUser", "84940",
+ EnumSet.allOf(HddsProtos.BlockTokenSecretProto.AccessModeProto.class),
+ expiryTime, cert.getSerialNumber().toString());
+ 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 OzoneBlockTokenIdentifier("", "",
+ EnumSet.allOf(HddsProtos.BlockTokenSecretProto.AccessModeProto.class),
+ expiryTime, cert.getSerialNumber().toString());
+ LOG.info("Unsigned token {} is {}", tokenId,
+ verifyTokenAsymmetric(tokenId, RandomUtils.nextBytes(128), cert));
+
+ }
+
+ public byte[] signTokenAsymmetric(OzoneBlockTokenIdentifier 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(OzoneBlockTokenIdentifier tokenId,
+ byte[] signature, Certificate certificate) throws InvalidKeyException,
+ NoSuchAlgorithmException, SignatureException {
+ Signature rsaSignature = Signature.getInstance("SHA256withRSA");
+ rsaSignature.initVerify(certificate);
+ rsaSignature.update(tokenId.getBytes());
+ boolean isValid = rsaSignature.verify(signature);
+ return isValid;
+ }
+
+ private byte[] signTokenSymmetric(OzoneBlockTokenIdentifier 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());
+ }
+
+ OzoneBlockTokenIdentifier generateTestToken() {
+ return new OzoneBlockTokenIdentifier(RandomStringUtils.randomAlphabetic(6),
+ RandomStringUtils.randomAlphabetic(5),
+ EnumSet.allOf(HddsProtos.BlockTokenSecretProto.AccessModeProto.class),
+ expiryTime, cert.getSerialNumber().toString());
+ }
+
+ @Test
+ public void testAsymmetricTokenPerf() throws NoSuchAlgorithmException,
+ CertificateEncodingException, NoSuchProviderException,
+ InvalidKeyException, SignatureException {
+ final int testTokenCount = 1000;
+ List<OzoneBlockTokenIdentifier> tokenIds = new ArrayList<>();
+ List<byte[]> tokenPasswordAsym = new ArrayList<>();
+ for (int i = 0; i < testTokenCount; i++) {
+ tokenIds.add(generateTestToken());
+ }
+
+ KeyPair kp = KeyStoreTestUtil.generateKeyPair("RSA");
+
+ // Create Ozone Master certificate (SCM CA issued cert) and key store
+ X509Certificate certificate;
+ certificate = KeyStoreTestUtil.generateCertificate("CN=OzoneMaster",
+ kp, 30, "SHA256withRSA");
+
+ long startTime = Time.monotonicNowNanos();
+ for (int i = 0; i < testTokenCount; i++) {
+ tokenPasswordAsym.add(
+ signTokenAsymmetric(tokenIds.get(i), kp.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),
+ certificate);
+ }
+ 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<OzoneBlockTokenIdentifier> 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);
+ }
+
+ // TODO: verify certificate with a trust store
+ public boolean verifyCert(Certificate certificate) {
+ return true;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneTokenIdentifier.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneTokenIdentifier.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneTokenIdentifier.java
new file mode 100644
index 0000000..dfdf14b
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestOzoneTokenIdentifier.java
@@ -0,0 +1,300 @@
+/**
+ * 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();
+ 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"));
+ 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() {
+ return new OzoneTokenIdentifier(
+ new Text(RandomStringUtils.randomAlphabetic(6)),
+ new Text(RandomStringUtils.randomAlphabetic(5)),
+ new Text(RandomStringUtils.randomAlphabetic(4)));
+ }
+
+ @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);
+ return id;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/2aad5e65/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/package-info.java
----------------------------------------------------------------------
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/package-info.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/package-info.java
new file mode 100644
index 0000000..a36f325
--- /dev/null
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/package-info.java
@@ -0,0 +1,21 @@
+/**
+ * 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;
+/**
+ * Ozone security tests.
+ */
---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org