/**
* 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.hive.llap.security;
import static org.junit.Assert.*;
import java.io.IOException;
import org.apache.hadoop.hive.llap.security.LlapSigner.Signable;
import org.apache.hadoop.hive.llap.security.LlapSigner.SignedMessage;
import org.apache.hadoop.io.DataInputBuffer;
import org.apache.hadoop.io.DataOutputBuffer;
import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier;
import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
import org.apache.hadoop.security.token.delegation.DelegationKey;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestLlapSignerImpl {
private static final Logger LOG = LoggerFactory.getLogger(TestLlapSignerImpl.class);
@Test(timeout = 10000)
public void testSigning() throws Exception {
FakeSecretManager fsm = new FakeSecretManager();
fsm.startThreads();
// Make sure the signature works.
LlapSignerImpl signer = new LlapSignerImpl(fsm);
byte theByte = 1;
TestSignable in = new TestSignable(theByte);
TestSignable in2 = new TestSignable(++theByte);
SignedMessage sm = signer.serializeAndSign(in);
SignedMessage sm2 = signer.serializeAndSign(in2);
TestSignable out = TestSignable.deserialize(sm.message);
TestSignable out2 = TestSignable.deserialize(sm2.message);
assertEquals(in, out);
assertEquals(in2, out2);
signer.checkSignature(sm.message, sm.signature, out.masterKeyId);
signer.checkSignature(sm2.message, sm2.signature, out2.masterKeyId);
// Make sure the broken signature doesn't work.
try {
signer.checkSignature(sm.message, sm2.signature, out.masterKeyId);
fail("Didn't throw");
} catch (SecurityException ex) {
// Expected.
}
int index = sm.signature.length / 2;
sm.signature[index] = (byte)(sm.signature[index] + 1);
try {
signer.checkSignature(sm.message, sm.signature, out.masterKeyId);
fail("Didn't throw");
} catch (SecurityException ex) {
// Expected.
}
sm.signature[index] = (byte)(sm.signature[index] - 1);
fsm = rollKey(fsm, out.masterKeyId);
signer = new LlapSignerImpl(fsm);
// Sign in2 with a different key.
sm2 = signer.serializeAndSign(in2);
out2 = TestSignable.deserialize(sm2.message);
assertNotEquals(out.masterKeyId, out2.masterKeyId);
assertEquals(in2, out2);
signer.checkSignature(sm2.message, sm2.signature, out2.masterKeyId);
signer.checkSignature(sm.message, sm.signature, out.masterKeyId);
// Make sure the key ID mismatch causes error.
try {
signer.checkSignature(sm2.message, sm2.signature, out.masterKeyId);
fail("Didn't throw");
} catch (SecurityException ex) {
// Expected.
}
// The same for rolling the key; re-create the fsm with only the key #2.
fsm = rollKey(fsm, out2.masterKeyId);
signer = new LlapSignerImpl(fsm);
signer.checkSignature(sm2.message, sm2.signature, out2.masterKeyId);
// The key is missing - shouldn't be able to verify.
try {
signer.checkSignature(sm.message, sm.signature, out.masterKeyId);
fail("Didn't throw");
} catch (SecurityException ex) {
// Expected.
}
fsm.stopThreads();
}
private FakeSecretManager rollKey(FakeSecretManager fsm, int idToPreserve) throws IOException {
// Adding keys is PITA - there's no way to plug into timed rolling; just create a new fsm.
DelegationKey dk = fsm.getDelegationKey(idToPreserve), curDk = fsm.getCurrentKey();
if (curDk.getKeyId() != idToPreserve) {
LOG.warn("The current key is not the one we expect; key rolled in background? Signed with "
+ idToPreserve + " but got " + curDk.getKeyId());
}
// Regardless of the above, we should have the key we've signed with.
assertNotNull(dk);
assertEquals(idToPreserve, dk.getKeyId());
fsm.stopThreads();
fsm = new FakeSecretManager();
fsm.addKey(dk);
assertNotNull("Couldn't add key", fsm.getDelegationKey(dk.getKeyId()));
fsm.startThreads();
return fsm;
}
private static class TestSignable implements Signable {
public int masterKeyId;
public byte index;
public TestSignable(byte i) {
index = i;
}
public TestSignable(int keyId, byte b) {
masterKeyId = keyId;
index = b;
}
@Override
public void setSignInfo(int masterKeyId) {
this.masterKeyId = masterKeyId;
}
@Override
public byte[] serialize() throws IOException {
DataOutputBuffer dob = new DataOutputBuffer(5);
dob.writeInt(masterKeyId);
dob.write(index);
byte[] b = dob.getData();
dob.close();
return b;
}
public static TestSignable deserialize(byte[] bytes) throws IOException {
DataInputBuffer db = new DataInputBuffer();
db.reset(bytes, bytes.length);
int keyId = db.readInt();
byte b = db.readByte();
db.close();
return new TestSignable(keyId, b);
}
@Override
public int hashCode() {
return 31 * index + masterKeyId;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof TestSignable)) return false;
TestSignable other = (TestSignable) obj;
return (index == other.index) && (masterKeyId == other.masterKeyId);
}
}
private static class FakeSecretManager
extends AbstractDelegationTokenSecretManager<AbstractDelegationTokenIdentifier>
implements SigningSecretManager {
public FakeSecretManager() {
super(10000000, 10000000, 10000000, 10000000);
}
@Override
public DelegationKey getCurrentKey() {
return getDelegationKey(getCurrentKeyId());
}
@Override
public DelegationKey getDelegationKey(int keyId) {
return super.getDelegationKey(keyId);
}
@Override
public byte[] signWithKey(byte[] message, DelegationKey key) {
return createPassword(message, key.getKey());
}
@Override
public byte[] signWithKey(byte[] message, int keyId) throws SecurityException {
DelegationKey key = getDelegationKey(keyId);
if (key == null) {
throw new SecurityException("The key ID " + keyId + " was not found");
}
return createPassword(message, key.getKey());
}
@Override
public AbstractDelegationTokenIdentifier createIdentifier() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
stopThreads();
}
}
}