/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.diqube.ticket; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import org.diqube.context.Profiles; import org.diqube.thrift.base.thrift.Ticket; import org.diqube.thrift.base.thrift.TicketClaim; import org.diqube.thrift.base.util.RUuidUtil; import org.diqube.ticket.TicketRsaKeyFileProvider.IOExceptionSupplier; import org.diqube.util.Triple; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * * @author Bastian Gloeckle */ public class TicketSignatureServiceTest { private static final String ENCRYPTED_PASSWORD = "diqube"; private static final Triple<String, IOExceptionSupplier<InputStream>, String> PRIVATE_ENCRYPTED = new Triple<>("private.enc", () -> TicketSignatureServiceTest.class .getResourceAsStream("/" + TicketSignatureServiceTest.class.getSimpleName() + "/private.enc.pem"), ENCRYPTED_PASSWORD); private static final Triple<String, IOExceptionSupplier<InputStream>, String> PRIVATE_PLAIN = new Triple<>("private.plain", () -> TicketSignatureServiceTest.class .getResourceAsStream("/" + TicketSignatureServiceTest.class.getSimpleName() + "/private.plain.pem"), null); private static final Triple<String, IOExceptionSupplier<InputStream>, String> PUBLIC_ENCRYPTED = new Triple<>("public.enc", () -> TicketSignatureServiceTest.class .getResourceAsStream("/" + TicketSignatureServiceTest.class.getSimpleName() + "/public.enc.pem"), ENCRYPTED_PASSWORD); private static final Triple<String, IOExceptionSupplier<InputStream>, String> PUBLIC_PLAIN = new Triple<>("public.plain", () -> TicketSignatureServiceTest.class .getResourceAsStream("/" + TicketSignatureServiceTest.class.getSimpleName() + "/public.plain.pem"), null); private AnnotationConfigApplicationContext dataContext; private TicketSignatureService ticketSignatureService; @BeforeMethod public void before() { } @AfterMethod public void after() { dataContext.close(); dataContext = null; ticketSignatureService = null; } @Test public void simpleSignAndValidateEncryptedKey() { synchronized (TestTicketRsaKeyFileProvider.class) { TestTicketRsaKeyFileProvider.clean(); TestTicketRsaKeyFileProvider.files.add(PRIVATE_ENCRYPTED); TestTicketRsaKeyFileProvider.isFilesWithPrivateKeyAreRequired = true; startNewContext(); // GIVEN Ticket t = new Ticket(); t.setClaim(new TicketClaim()); t.getClaim().setTicketId(RUuidUtil.toRUuid(UUID.randomUUID())); t.getClaim().setUsername("abc"); t.getClaim().setValidUntil(123); t.getClaim().setIsSuperUser(false); // WHEN ticketSignatureService.signTicket(t); boolean isValid = isValid(t); // THEN Assert.assertTrue(isValid, "Ticket should be valid."); } } @Test public void simpleSignAndValidatePlainKey() { synchronized (TestTicketRsaKeyFileProvider.class) { TestTicketRsaKeyFileProvider.clean(); TestTicketRsaKeyFileProvider.files.add(PRIVATE_PLAIN); TestTicketRsaKeyFileProvider.isFilesWithPrivateKeyAreRequired = true; startNewContext(); // GIVEN Ticket t = new Ticket(); t.setClaim(new TicketClaim()); t.getClaim().setTicketId(RUuidUtil.toRUuid(UUID.randomUUID())); t.getClaim().setUsername("abc"); t.getClaim().setValidUntil(123); t.getClaim().setIsSuperUser(false); // WHEN ticketSignatureService.signTicket(t); boolean isValid = isValid(t); // THEN Assert.assertTrue(isValid, "Ticket should be valid."); } } @Test public void signAndValidatePublicPlainKey() { synchronized (TestTicketRsaKeyFileProvider.class) { TestTicketRsaKeyFileProvider.clean(); TestTicketRsaKeyFileProvider.files.add(PRIVATE_PLAIN); TestTicketRsaKeyFileProvider.isFilesWithPrivateKeyAreRequired = true; startNewContext(); // GIVEN Ticket t = new Ticket(); t.setClaim(new TicketClaim()); t.getClaim().setTicketId(RUuidUtil.toRUuid(UUID.randomUUID())); t.getClaim().setUsername("abc"); t.getClaim().setValidUntil(123); t.getClaim().setIsSuperUser(false); ticketSignatureService.signTicket(t); // WHEN TestTicketRsaKeyFileProvider.clean(); TestTicketRsaKeyFileProvider.files.add(PUBLIC_PLAIN); TestTicketRsaKeyFileProvider.isFilesWithPrivateKeyAreRequired = false; startNewContext(); boolean isValid = isValid(t); // THEN Assert.assertTrue(isValid, "Ticket should be valid."); } } @Test public void signAndValidatePublicEncryptedKey() { synchronized (TestTicketRsaKeyFileProvider.class) { TestTicketRsaKeyFileProvider.clean(); TestTicketRsaKeyFileProvider.files.add(PRIVATE_ENCRYPTED); TestTicketRsaKeyFileProvider.isFilesWithPrivateKeyAreRequired = true; startNewContext(); // GIVEN Ticket t = new Ticket(); t.setClaim(new TicketClaim()); t.getClaim().setTicketId(RUuidUtil.toRUuid(UUID.randomUUID())); t.getClaim().setUsername("abc"); t.getClaim().setValidUntil(123); t.getClaim().setIsSuperUser(false); ticketSignatureService.signTicket(t); // WHEN TestTicketRsaKeyFileProvider.clean(); TestTicketRsaKeyFileProvider.files.add(PUBLIC_ENCRYPTED); TestTicketRsaKeyFileProvider.isFilesWithPrivateKeyAreRequired = false; startNewContext(); boolean isValid = isValid(t); // THEN Assert.assertTrue(isValid, "Ticket should be valid."); } } @Test public void signAndValidateEncryptedKeyTampered() { synchronized (TestTicketRsaKeyFileProvider.class) { TestTicketRsaKeyFileProvider.clean(); TestTicketRsaKeyFileProvider.files.add(PRIVATE_ENCRYPTED); TestTicketRsaKeyFileProvider.isFilesWithPrivateKeyAreRequired = true; startNewContext(); // GIVEN Ticket t = new Ticket(); t.setClaim(new TicketClaim()); t.getClaim().setTicketId(RUuidUtil.toRUuid(UUID.randomUUID())); t.getClaim().setUsername("abc"); t.getClaim().setValidUntil(123); t.getClaim().setIsSuperUser(false); ticketSignatureService.signTicket(t); // WHEN byte[] sig = t.getSignature(); sig[0] = (byte) (sig[0] + 1); t.setSignature(sig); boolean isValid = isValid(t); // THEN Assert.assertFalse(isValid, "Ticket should NOT be valid."); } } @Test public void signAndValidateEncryptedKeyTampered2() { synchronized (TestTicketRsaKeyFileProvider.class) { TestTicketRsaKeyFileProvider.clean(); TestTicketRsaKeyFileProvider.files.add(PRIVATE_ENCRYPTED); TestTicketRsaKeyFileProvider.isFilesWithPrivateKeyAreRequired = true; startNewContext(); // GIVEN Ticket t = new Ticket(); t.setClaim(new TicketClaim()); t.getClaim().setTicketId(RUuidUtil.toRUuid(UUID.randomUUID())); t.getClaim().setUsername("abc"); t.getClaim().setValidUntil(123); t.getClaim().setIsSuperUser(false); ticketSignatureService.signTicket(t); // WHEN t.getClaim().setUsername("xyz"); boolean isValid = isValid(t); // THEN Assert.assertFalse(isValid, "Ticket should NOT be valid."); } } private boolean isValid(Ticket t) { return ticketSignatureService .isValidTicketSignature(TicketUtil.deserialize(ByteBuffer.wrap(TicketUtil.serialize(t)))); } private void startNewContext() { dataContext = new AnnotationConfigApplicationContext(); dataContext.getEnvironment().setActiveProfiles(Profiles.UNIT_TEST); dataContext.scan("org.diqube"); dataContext.registerBeanDefinition("a", new RootBeanDefinition(TestTicketRsaKeyFileProvider.class)); dataContext.refresh(); ticketSignatureService = dataContext.getBean(TicketSignatureService.class); } public static class TestTicketRsaKeyFileProvider implements TicketRsaKeyFileProvider { static boolean isFilesWithPrivateKeyAreRequired; static List<Triple<String, IOExceptionSupplier<InputStream>, String>> files; static void clean() { isFilesWithPrivateKeyAreRequired = false; files = new ArrayList<>(); } @Override public CompletableFuture<List<Triple<String, IOExceptionSupplier<InputStream>, String>>> getPemFiles() { return CompletableFuture.completedFuture(files); } @Override public boolean filesWithPrivateKeyAreRequired() { return isFilesWithPrivateKeyAreRequired; } } }