org.jboss.audit.log.tamper.detecting.TrustedLocation.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.audit.log.tamper.detecting.TrustedLocation.java

Source

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.audit.log.tamper.detecting;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.Collections;

import javax.crypto.Cipher;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequenceGenerator;
import org.jboss.audit.log.LogFileNameUtil;
import org.jboss.audit.log.tamper.detecting.LogReader.LogInfo;
import org.jboss.audit.log.tamper.detecting.RecoverableErrorCondition.RecoverAction;
import org.jboss.audit.log.tamper.detecting.RecoverableErrorContext.RecoverCallback;

/**
 *
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 */
class TrustedLocation {

    private final ServerKeyManager keyManager;
    private final File trustedLocationFile;
    private final File previousLogFile;
    private final File currentInspectionLogFile;
    private final byte[] lastAccumulativeHash;
    private final int lastSequenceNumber;
    private final boolean inspectingPreviousLogFile;

    private TrustedLocation(ServerKeyManager keyManager, File logFileDir, File trustedLocationFile,
            File previousLogFile, File currentInspectionLogFile, int lastSequenceNumber,
            byte[] lastAccumulativeHash, boolean inspectingPreviousLogFile) {
        this.keyManager = keyManager;
        this.trustedLocationFile = trustedLocationFile;
        this.previousLogFile = previousLogFile;
        this.currentInspectionLogFile = currentInspectionLogFile;
        this.lastAccumulativeHash = lastAccumulativeHash;
        this.lastSequenceNumber = lastSequenceNumber;
        this.inspectingPreviousLogFile = inspectingPreviousLogFile;
    }

    static TrustedLocation create(final RecoverableErrorContext recoverableContext,
            final ServerKeyManager keyManager, final File logFileDir, final File trustedLocationFile)
            throws RecoverableException {
        final LogFileNameUtil logFileNameUtil = new LogFileNameUtil(logFileDir);
        final File lastLogFile = logFileNameUtil.getPreviousLogFilename(null);
        File currentInspectionFile = null;
        byte[] lastAccumulativeHash = null;
        int lastSequenceNumber = 0;
        boolean inspectingPreviousLogFile = false;
        if (!trustedLocationFile.exists()) {
            recoverableContext.trustedLocationDoesNotExistWhileLogFilesExist(trustedLocationFile,
                    Collections.singletonMap(RecoverAction.CREATE_TRUSTED_LOCATION,
                            RecreateTrustedLocationCallback.create(trustedLocationFile)));
        } else {
            Content content = null;
            try {
                content = Content.read(keyManager, trustedLocationFile);
            } catch (Exception e) {
                recoverableContext.trustedLocationExistsButIsCorrupt(trustedLocationFile, e,
                        Collections.singletonMap(RecoverAction.REBUILD_TRUSTED_LOCATION,
                                RecreateTrustedLocationCallback.create(trustedLocationFile)));
            }

            if (content != null) {
                currentInspectionFile = new File(logFileDir, content.lastFileName);
                if (!currentInspectionFile.exists()) {
                    currentInspectionFile = recoverableContext.trustedLocationCurrentFileDoesNotExist(
                            currentInspectionFile.getName(), lastLogFile,
                            Collections.singletonMap(RecoverAction.INSPECT_LAST_LOG_FILE,
                                    (RecoverCallback<File>) new RecoverCallback<File>() {
                                        @Override
                                        public File repair() {
                                            return lastLogFile;
                                        }
                                    }));
                    inspectingPreviousLogFile = true;
                }
                lastAccumulativeHash = content.lastAccumulativeHash;
                lastSequenceNumber = content.lastSequenceNumber;
            }
        }

        TrustedLocation trustedLocation = new TrustedLocation(keyManager, logFileDir, trustedLocationFile,
                lastLogFile, currentInspectionFile, lastSequenceNumber, lastAccumulativeHash,
                inspectingPreviousLogFile);
        return trustedLocation;
    }

    File getPreviousLogFile() {
        return previousLogFile;
    }

    File getCurrentInspectionLogFile() {
        return currentInspectionLogFile;
    }

    byte[] getAccumulatedMessageHash() {
        return lastAccumulativeHash;
    }

    int write(File logFile, int sequenceNumber, byte[] accumulatedHash) {
        byte[] fileBytes = null;
        byte[] asn1Block = generateASN1Block(logFile.getName(), sequenceNumber, accumulatedHash);
        if (asn1Block == null) {
            throw new RuntimeException("Could not generate ASN1 block when writing trusted location");
        }

        try {
            if (!trustedLocationFile.exists()) {
                trustedLocationFile.createNewFile();
            }
            RandomAccessFile raf = new RandomAccessFile(trustedLocationFile, "rw");
            raf.setLength(0);
            try {
                Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
                cipher.init(Cipher.ENCRYPT_MODE, keyManager.getSecretKey(), keyManager.getPbeParameterSpec());
                fileBytes = cipher.doFinal(asn1Block);
                raf.write(fileBytes);
            } finally {
                IoUtils.safeClose(raf);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return 1;
    }

    void checkLastLogRecord(final RecoverableErrorContext recoverableContext, final LogInfo lastLogInfo)
            throws RecoverableException, ValidationException {
        if (currentInspectionLogFile != null && !inspectingPreviousLogFile) {
            if (lastLogInfo.getLastSequenceNumber() != lastSequenceNumber) {
                if (lastLogInfo.getLastSequenceNumber() == lastSequenceNumber + 1) {
                    recoverableContext.possibleCrashBetweenWritingLogRecordAndUpdatingTrustedLocation(
                            lastLogInfo.getLogFile(), lastLogInfo.getLastSequenceNumber(), lastSequenceNumber,
                            Collections.singletonMap(RecoverAction.REPAIR_TRUSTED_LOCATION,
                                    (RecoverCallback<Void>) new RecoverCallback<Void>() {
                                        @Override
                                        public Void repair() {
                                            write(currentInspectionLogFile, lastLogInfo.getLastSequenceNumber(),
                                                    lastLogInfo.getAccumulativeDigest().getAccumulativeHash());
                                            return null;
                                        }
                                    }));
                    return;
                } else {
                    throw new ValidationException("The sequence number in " + currentInspectionLogFile + " was "
                            + lastLogInfo.getLastSequenceNumber() + " but the trusted location has this as "
                            + this.lastSequenceNumber);
                }
            } else if (lastLogInfo.getAccumulatedHash() != null
                    && !Arrays.equals(lastLogInfo.getAccumulatedHash(), this.lastAccumulativeHash)) {
                throw new ValidationException("The accumulated hash is different in " + currentInspectionLogFile
                        + " and in the trusted location");
            }

            if (lastLogInfo.getAccumulatedHash() == null) {
                recoverableContext.lastLogFileDoesNotHaveAnAccumulatedHash(lastLogInfo.getLogFile(),
                        Collections.singletonMap(RecoverAction.REPAIR_MISSING_ACCUMULATED_HASH,
                                (RecoverCallback<Void>) new RecoverCallback<Void>() {
                                    @Override
                                    public Void repair() {
                                        try {
                                            SecureAuditLogWriter
                                                    .createForFixing(keyManager, lastLogInfo.getLogFile(),
                                                            TrustedLocation.this,
                                                            lastLogInfo.getAccumulativeDigest(), lastSequenceNumber,
                                                            lastLogInfo.getLastRecordLength())
                                                    .writeMissingAccumulatedHashAndSignatureRecordsAndCloseWriter();
                                        } catch (IOException e) {
                                            throw new RuntimeException(e);
                                        }
                                        return null;
                                    }
                                }));
            } else if (lastLogInfo.getSignature() == null) {
                recoverableContext.lastLogFileDoesNotHaveASignature(lastLogInfo.getLogFile(),
                        Collections.singletonMap(RecoverAction.REPAIR_MISSING_SIGNATURE,
                                (RecoverCallback<Void>) new RecoverCallback<Void>() {
                                    @Override
                                    public Void repair() {
                                        try {
                                            SecureAuditLogWriter
                                                    .createForFixing(keyManager, lastLogInfo.getLogFile(),
                                                            TrustedLocation.this,
                                                            lastLogInfo.getAccumulativeDigest(), lastSequenceNumber,
                                                            lastLogInfo.getLastRecordLength())
                                                    .writeMissingSignatureRecordAndCloseWriter();
                                        } catch (IOException e) {
                                            throw new RuntimeException(e);
                                        }
                                        return null;
                                    }
                                }));
                return;
            }
        }
    }

    private byte[] generateASN1Block(String logFileName, int sequenceNumber, byte[] accumulatedHash) {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        try {
            DERSequenceGenerator gen = new DERSequenceGenerator(bout);
            gen.addObject(new DERIA5String(logFileName));
            gen.addObject(new ASN1Integer(sequenceNumber));
            gen.addObject(new DEROctetString(accumulatedHash));
            gen.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return bout.toByteArray();
    }

    static class Content {
        private volatile String lastFileName;
        private volatile int lastSequenceNumber;
        private volatile byte[] lastAccumulativeHash;

        private Content(String lastFileName, int lastSequenceNumber, byte[] lastAccumulativeHash) {
            this.lastFileName = lastFileName;
            this.lastSequenceNumber = lastSequenceNumber;
            this.lastAccumulativeHash = lastAccumulativeHash;
        }

        private static Content read(ServerKeyManager keyManager, File trustedLocationFile) throws Exception {
            byte[] fileBytes = new byte[(int) trustedLocationFile.length()];
            byte[] decryptedBytes = null;
            final RandomAccessFile raf = new RandomAccessFile(trustedLocationFile, "rw");
            try {
                raf.read(fileBytes);
                Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
                cipher.init(Cipher.DECRYPT_MODE, keyManager.getSecretKey(), keyManager.getPbeParameterSpec());
                decryptedBytes = cipher.doFinal(fileBytes);
                return extractASN1Block(decryptedBytes);
            } finally {
                IoUtils.safeClose(raf);
            }
        }

        private static Content extractASN1Block(byte[] asn1Block) throws Exception {
            ASN1InputStream aIn = new ASN1InputStream(asn1Block);
            try {
                ASN1Sequence sequence = (ASN1Sequence) aIn.readObject();
                String lastFileName = ((DERIA5String) sequence.getObjectAt(0)).getString();
                int lastSequenceNumber = ((ASN1Integer) sequence.getObjectAt(1)).getValue().intValue();
                byte[] lastAccumulativeHash = ((DEROctetString) sequence.getObjectAt(2)).getOctets();

                return new Content(lastFileName, lastSequenceNumber, lastAccumulativeHash);
            } finally {
                IoUtils.safeClose(aIn);
            }
        }
    }

    private static class RecreateTrustedLocationCallback implements RecoverCallback<Void> {

        private final File trustedLocationFile;

        private RecreateTrustedLocationCallback(final File trustedLocationFile) {
            this.trustedLocationFile = trustedLocationFile;
        }

        static RecoverCallback<Void> create(final File trustedLocationFile) {
            return new RecreateTrustedLocationCallback(trustedLocationFile);
        }

        @Override
        public Void repair() {
            boolean created = false;
            try {
                trustedLocationFile.delete();
                created = trustedLocationFile.createNewFile();
                if (!created) {
                    created = trustedLocationFile.exists();
                }
            } catch (IOException e) {
            }
            if (!created) {
                throw new IllegalStateException("Failed to create " + trustedLocationFile);
            }
            return null;
        }

    }
}