com.otterca.persistence.dao.X509CertificateDaoDatastore.java Source code

Java tutorial

Introduction

Here is the source code for com.otterca.persistence.dao.X509CertificateDaoDatastore.java

Source

/*
 * This code was written by Bear Giles <bgiles@otterca.com>and he
 * 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
 *
 * Any contributions made by others are licensed to this project under
 * one or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.
 * 
 * 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.
 * 
 * Copyright (c) 2012 Bear Giles <bgiles@otterca.com>
 */
package com.otterca.persistence.dao;

import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.ParametersAreNonnullByDefault;

import org.springframework.beans.factory.annotation.Autowired;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.otterca.common.crypto.X509CertificateUtil;

/**
 * Implementation of X509CertificateDao.
 * 
 * Implementation note: delete certificates? don't want to do it in production
 * database but do want to do it in test databases.
 * 
 * @author bgiles@otterca.com
 */
@ParametersAreNonnullByDefault
public class X509CertificateDaoDatastore implements X509CertificateDao {
    private static final String KIND = "application/x509"; // ???
    private static final String CERTIFICATE = "certificate";
    private static final String SERIAL_NUMBER = "serialNumber";
    private static final String SUBJECT_DN = "subjectDN";
    private static final String ISSUER_DN = "issuerDN";
    private static final String NOT_BEFORE = "notBefore";
    private static final String NOT_AFTER = "notAfter";

    private static final String COMMON_NAME = "commonName";
    private static final String FINGERPRINT = "fingerprint";
    private static final String CERT_HASH = "certHash";
    private static final String SKID_HASH = "skidHash";
    private static final String AKID_HASH = "akidHash";
    private static final String SUBJECT_HASH = "sHash";
    private static final String ISSUER_HASH = "iHash";

    private static final String STATUS = "status";
    private static final String TRUSTED = "trusted";

    // TODO: pull this from enum.
    private static final String UNKNOWN = "unknown";
    // private static final String ACTIVE = "active";
    // private static final String EXPIRED = "expired";
    // private static final String REVOKED = "revoked";
    // private static final String NOT_YET_READY = "not_yet_ready";

    @Autowired
    private X509CertificateUtil x509CertUtil;

    @Autowired
    private DatastoreService datastore;

    /**
     * Default constructor.
     */
    public X509CertificateDaoDatastore() {
    }

    /**
     * Constructor used during testing.
     */
    X509CertificateDaoDatastore(DatastoreService datastore, X509CertificateUtil x509CertUtil) {
        this.datastore = datastore;
        this.x509CertUtil = x509CertUtil;
    }

    /**
     * Generate standard key.
     * 
     * @param cert
     * @return
     */
    public Key generateKey(X509Certificate cert) {
        return KeyFactory.createKey(KIND, cert.getIssuerDN() + ":" + cert.getSerialNumber().toString(16));
    }

    /**
     * Generate standard key.
     * 
     * @param issuerDN
     * @param serialNumber
     * @return
     */
    public Key generateKey(String issuerDN, BigInteger serialNumber) {
        return KeyFactory.createKey(KIND, issuerDN + ":" + serialNumber.toString(16));
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#put(java.security.cert
     *      .X509Certificate)
     */
    public void put(X509Certificate cert) throws IOException, CertificateEncodingException {

        // TODO: we want cert's issuer to be its parent. For now certs don't
        // have parents.
        Key key = generateKey(cert);
        Entity e = new Entity(key);

        // also set parent...

        e.setProperty(CERTIFICATE, new Blob(cert.getEncoded()));
        // up to 20 octets - 40 characters
        e.setProperty(SERIAL_NUMBER, cert.getSerialNumber().toString(16));
        // up to 500 unicode characters
        e.setProperty(SUBJECT_DN, cert.getSubjectDN().getName());
        // up to 500 unicode characters
        e.setProperty(ISSUER_DN, cert.getIssuerDN().getName());
        e.setProperty(NOT_BEFORE, cert.getNotBefore());
        e.setProperty(NOT_AFTER, cert.getNotAfter());

        // RFC search criteria
        e.setProperty(COMMON_NAME, x509CertUtil.getName(cert));
        e.setProperty(FINGERPRINT, x509CertUtil.getFingerprint(cert));
        e.setProperty(CERT_HASH, x509CertUtil.getCertificateHash(cert));
        e.setProperty(ISSUER_HASH, x509CertUtil.getIHash(cert));
        e.setProperty(SUBJECT_HASH, x509CertUtil.getSHash(cert));
        // e.setProperty(AKID_HASH, x509CertUtil.getAkidHash(cert));
        e.setProperty(SKID_HASH, x509CertUtil.getSkidHash(cert));
        // e.setProperty(IANDS_HASH, x509CertUtil.getIandSHash(cert));

        // e.setProperty(EMAIL) ?...

        e.setUnindexedProperty(TRUSTED, false);
        e.setUnindexedProperty(STATUS, UNKNOWN);

        datastore.put(e);
    }

    /**
     * Verify that cached results are consistent. It's a strong indication that
     * someone has been screwing with the database if the values are
     * inconsistent. This is computationally expensive but the cost of a
     * corrupted database is far worse.
     * 
     * @param entity
     * @param cert
     */
    public void validate(Entity entity, X509Certificate cert) throws CertificateException {
        if (!cert.getSerialNumber().equals(entity.getProperty(SERIAL_NUMBER))) {
            throw new CertificateException("serial number did not match");
        }
        if (!cert.getIssuerDN().equals(entity.getProperty(ISSUER_DN))) {
            throw new CertificateException("issuer dn did not match");
        }
        if (!cert.getSubjectDN().equals(entity.getProperty(SUBJECT_DN))) {
            throw new CertificateException("subject dn did not match");
        }
        if (!cert.getNotBefore().equals(entity.getProperty(NOT_BEFORE))) {
            throw new CertificateException("notBefore did not match");
        }
        if (!cert.getNotAfter().equals(entity.getProperty(NOT_AFTER))) {
            throw new CertificateException("notAfter did not match");
        }
        if (!x509CertUtil.getName(cert).equals(entity.getProperty(COMMON_NAME))) {
            throw new CertificateException("common name did not match");
        }
        if (!x509CertUtil.getFingerprint(cert).equals(entity.getProperty(FINGERPRINT))) {
            throw new CertificateException("cached fingerprints did not match");
        }
        if (!x509CertUtil.getCertificateHash(cert).equals(entity.getProperty(CERT_HASH))) {
            throw new CertificateException("cached certificate hash did not match");
        }
        if (!x509CertUtil.getIHash(cert).equals(entity.getProperty(ISSUER_HASH))) {
            throw new CertificateException("cached issuer hash did not match");
        }
        if (!x509CertUtil.getSHash(cert).equals(entity.getProperty(SUBJECT_HASH))) {
            throw new CertificateException("cached subject hash did not match");
        }
        if (!x509CertUtil.getAkidHash(cert).equals(entity.getProperty(AKID_HASH))) {
            throw new CertificateException("cached AKID hash did not match");
        }
        if (!x509CertUtil.getSkidHash(cert).equals(entity.getProperty(SKID_HASH))) {
            throw new CertificateException("cached SKID hash did not match");
        }
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#getCertificate(java.lang.String,
     *      java.lang.Integer)
     */
    public X509Certificate getCertificate(String issuerDN, BigInteger serialNumber) throws CertificateException {
        return getCertificate(generateKey(issuerDN, serialNumber));
    }

    /**
     * 
     * @param key
     * @return
     * @throws CertificateException
     */
    public X509Certificate getCertificate(Key key) throws CertificateException {
        X509Certificate cert = null;
        try {
            Entity entity = datastore.get(key);
            Blob blob = (Blob) entity.getProperty(CERTIFICATE);
            cert = x509CertUtil.getCertificate(blob.getBytes());

            validate(entity, cert);
        } catch (EntityNotFoundException e) {
            // log miss
        }
        return cert;
    }

    /**
     * Find matching certificates.
     */
    public List<X509Certificate> findByQuery(Query query) throws CertificateException {
        List<X509Certificate> results = new ArrayList<X509Certificate>();
        for (Entity entity : datastore.prepare(query).asIterable()) {
            Blob blob = (Blob) entity.getProperty(CERTIFICATE);
            X509Certificate cert = x509CertUtil.getCertificate(blob.getBytes());
            validate(entity, cert);
            results.add(cert);
        }
        return results;
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#findByCommonName(java.lang.String)
     */
    @Override
    public List<X509Certificate> findByCommonName(String commonName) throws CertificateException {
        Query query = new Query(KIND);
        query.addFilter(COMMON_NAME, FilterOperator.EQUAL, commonName);
        List<X509Certificate> certificates = findByQuery(query);

        // verify results
        for (X509Certificate cert : certificates) {
            if (!x509CertUtil.getName(cert).equals(commonName)) {
                throw new IllegalStateException(
                        "results of database were not consistent for common name '" + commonName + "'");
            }
        }

        return certificates;
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#findByFingerprint(java.lang.String)
     */
    @Override
    public List<X509Certificate> findByFingerprint(String fingerprint) throws CertificateException {
        Query query = new Query(KIND);
        query.addFilter(FINGERPRINT, FilterOperator.EQUAL, fingerprint);
        List<X509Certificate> certificates = findByQuery(query);

        // verify results
        for (X509Certificate cert : certificates) {
            if (!x509CertUtil.getFingerprint(cert).equals(fingerprint)) {
                throw new IllegalStateException(
                        "results of database were not consistent for fingerprint '" + fingerprint + "'");
            }
        }

        return certificates;
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#findByCertificateHash(java.lang.String)
     */
    @Override
    public List<X509Certificate> findByCertificateHash(String hash) throws CertificateException {
        Query query = new Query(KIND);
        query.addFilter(CERT_HASH, FilterOperator.EQUAL, hash);
        List<X509Certificate> certificates = findByQuery(query);

        // verify results
        for (X509Certificate cert : certificates) {
            if (!x509CertUtil.getCertificateHash(cert).equals(hash)) {
                throw new IllegalStateException(
                        "results of database were not consistent for certificate hash '" + hash + "'");
            }
        }

        return certificates;
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#findByIHash(java.lang.String)
     */
    @Override
    public List<X509Certificate> findByIHash(String hash) throws CertificateException {
        Query query = new Query(KIND);
        query.addFilter(ISSUER_HASH, FilterOperator.EQUAL, hash);
        List<X509Certificate> certificates = findByQuery(query);

        // verify results
        for (X509Certificate cert : certificates) {
            if (!x509CertUtil.getIHash(cert).equals(hash)) {
                throw new IllegalStateException(
                        "results of database were not consistent for issuer hash '" + hash + "'");
            }
        }

        return certificates;
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#findBySHash(java.lang.String)
     */
    @Override
    public List<X509Certificate> findBySHash(String hash) throws CertificateException {
        Query query = new Query(KIND);
        query.addFilter(SUBJECT_HASH, FilterOperator.EQUAL, hash);
        List<X509Certificate> certificates = findByQuery(query);

        // verify results
        for (X509Certificate cert : certificates) {
            if (!x509CertUtil.getSHash(cert).equals(hash)) {
                throw new IllegalStateException(
                        "results of database were not consistent for subject hash '" + hash + "'");
            }
        }

        return certificates;
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#findByAkidHash(java.lang.String)
     */
    @Override
    public List<X509Certificate> findByAkidHash(String hash) throws CertificateException {
        Query query = new Query(KIND);
        query.addFilter(AKID_HASH, FilterOperator.EQUAL, hash);
        List<X509Certificate> certificates = findByQuery(query);

        // verify results
        for (X509Certificate cert : certificates) {
            if (!x509CertUtil.getAkidHash(cert).equals(hash)) {
                throw new IllegalStateException(
                        "results of database were not consistent for AKID hash '" + hash + "'");
            }
        }

        return certificates;
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#findBySkidHash(java.lang.String)
     */
    @Override
    public List<X509Certificate> findBySkidHash(String hash) throws CertificateException {
        Query query = new Query(KIND);
        query.addFilter(SKID_HASH, FilterOperator.EQUAL, hash);
        List<X509Certificate> certificates = findByQuery(query);

        // verify results
        for (X509Certificate cert : certificates) {
            if (!x509CertUtil.getSkidHash(cert).equals(hash)) {
                throw new IllegalStateException(
                        "results of database were not consistent for SKID hash '" + hash + "'");
            }
        }

        return certificates;
    }

    /**
     * @see com.otterca.persistence.dao.X509CertificateDao#deleteCertificate(java.lang.String,
     *      java.lang.Integer)
     */
    public void deleteCertificate(String issuerDN, BigInteger serialNumber) throws CertificateException {
        deleteCertificate(generateKey(issuerDN, serialNumber));
    }

    /**
     * 
     * @param key
     * @return
     * @throws CertificateException
     */
    public void deleteCertificate(Key key) throws CertificateException {
        datastore.delete(key);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.otterca.persistence.dao.X509CertificateDao#trust(java.lang.String,
     * java.math.BigInteger)
     */
    @Override
    public void trust(String issuerDN, BigInteger serialNumber) {
        // TODO Auto-generated method stub

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.otterca.persistence.dao.X509CertificateDao#distrust(java.lang.String,
     * java.math.BigInteger)
     */
    @Override
    public void distrust(String issuerDN, BigInteger serialNumber) {
        // TODO Auto-generated method stub

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.otterca.persistence.dao.X509CertificateDao#isTrusted(java.lang.String
     * , java.math.BigInteger)
     */
    @Override
    public Boolean isTrusted(String issuerDN, BigInteger serialNumber) {
        // TODO Auto-generated method stub
        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.otterca.persistence.dao.X509CertificateDao#revoked(java.lang.String,
     * java.math.BigInteger, java.lang.String)
     */
    @Override
    public void revoked(String issuerDN, BigInteger serialNumber, String reason) {
        // TODO Auto-generated method stub

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.otterca.persistence.dao.X509CertificateDao#isRevoked(java.lang.String
     * , java.math.BigInteger)
     */
    @Override
    public Boolean isRevoked(String issuerDN, BigInteger serialNumber) {
        // TODO Auto-generated method stub
        return false;
    }
}