com.google.gerrit.server.contact.EncryptedContactStore.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.server.contact.EncryptedContactStore.java

Source

// Copyright (C) 2009 The Android Open Source Project
//
// Licensed 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 com.google.gerrit.server.contact;

import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.errors.ContactInformationStoreException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.ContactInformation;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.UrlEncoded;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;

/** Encrypts {@link ContactInformation} instances and saves them. */
@Singleton
class EncryptedContactStore implements ContactStore {
    private static final Logger log = LoggerFactory.getLogger(EncryptedContactStore.class);
    private static final TimeZone UTC = TimeZone.getTimeZone("UTC");

    private final SchemaFactory<ReviewDb> schema;
    private final PGPPublicKey dest;
    private final SecureRandom prng;
    private final URL storeUrl;
    private final String storeAPPSEC;
    private final ContactStoreConnection.Factory connFactory;

    EncryptedContactStore(final URL storeUrl, final String storeAPPSEC, final File pubKey,
            final SchemaFactory<ReviewDb> schema, final ContactStoreConnection.Factory connFactory) {
        this.storeUrl = storeUrl;
        this.storeAPPSEC = storeAPPSEC;
        this.schema = schema;
        this.dest = selectKey(readPubRing(pubKey));
        this.connFactory = connFactory;

        final String prngName = "SHA1PRNG";
        try {
            prng = SecureRandom.getInstance(prngName);
        } catch (NoSuchAlgorithmException e) {
            throw new ProvisionException("Cannot create " + prngName, e);
        }

        // Run a test encryption to verify the proper algorithms exist in
        // our JVM and we are able to invoke them. This helps to identify
        // a system configuration problem early at startup, rather than a
        // lot later during runtime.
        //
        try {
            encrypt("test", new Date(0), "test".getBytes("UTF-8"));
        } catch (NoSuchProviderException | PGPException | IOException e) {
            throw new ProvisionException("PGP encryption not available", e);
        }
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    private static PGPPublicKeyRingCollection readPubRing(final File pub) {
        try (InputStream fin = new FileInputStream(pub); InputStream in = PGPUtil.getDecoderStream(fin)) {
            return new PGPPublicKeyRingCollection(in);
        } catch (IOException | PGPException e) {
            throw new ProvisionException("Cannot read " + pub, e);
        }
    }

    private static PGPPublicKey selectKey(final PGPPublicKeyRingCollection rings) {
        for (final Iterator<?> ri = rings.getKeyRings(); ri.hasNext();) {
            final PGPPublicKeyRing currRing = (PGPPublicKeyRing) ri.next();
            for (final Iterator<?> ki = currRing.getPublicKeys(); ki.hasNext();) {
                final PGPPublicKey k = (PGPPublicKey) ki.next();
                if (k.isEncryptionKey()) {
                    return k;
                }
            }
        }
        return null;
    }

    @Override
    public void store(final Account account, final ContactInformation info)
            throws ContactInformationStoreException {
        try {
            final byte[] plainText = format(account, info).getBytes("UTF-8");
            final String fileName = "account-" + account.getId();
            final Date fileDate = account.getContactFiledOn();
            final byte[] encText = encrypt(fileName, fileDate, plainText);
            final String encStr = new String(encText, "UTF-8");

            final Timestamp filedOn = account.getContactFiledOn();
            final UrlEncoded u = new UrlEncoded();
            if (storeAPPSEC != null) {
                u.put("APPSEC", storeAPPSEC);
            }
            if (account.getPreferredEmail() != null) {
                u.put("email", account.getPreferredEmail());
            }
            if (filedOn != null) {
                u.put("filed", String.valueOf(filedOn.getTime() / 1000L));
            }
            u.put("account_id", String.valueOf(account.getId().get()));
            u.put("data", encStr);
            connFactory.open(storeUrl).store(u.toString().getBytes("UTF-8"));
        } catch (IOException | PGPException | NoSuchProviderException e) {
            log.error("Cannot store encrypted contact information", e);
            throw new ContactInformationStoreException(e);
        }
    }

    @SuppressWarnings("deprecation")
    private final PGPEncryptedDataGenerator cpk() throws NoSuchProviderException, PGPException {
        PGPEncryptedDataGenerator cpk = new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, true, prng, "BC");
        cpk.addMethod(dest);
        return cpk;
    }

    private byte[] encrypt(final String name, final Date date, final byte[] rawText)
            throws NoSuchProviderException, PGPException, IOException {
        final byte[] zText = compress(name, date, rawText);

        final ByteArrayOutputStream buf = new ByteArrayOutputStream();
        final ArmoredOutputStream aout = new ArmoredOutputStream(buf);
        final OutputStream cout = cpk().open(aout, zText.length);
        cout.write(zText);
        cout.close();
        aout.close();

        return buf.toByteArray();
    }

    private static byte[] compress(final String fileName, Date fileDate, final byte[] plainText)
            throws IOException {
        final ByteArrayOutputStream buf = new ByteArrayOutputStream();
        final PGPCompressedDataGenerator comdg;
        final int len = plainText.length;
        if (fileDate == null) {
            fileDate = PGPLiteralData.NOW;
        }

        comdg = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
        final OutputStream out = new PGPLiteralDataGenerator().open(comdg.open(buf), PGPLiteralData.BINARY,
                fileName, len, fileDate);
        out.write(plainText);
        out.close();
        comdg.close();
        return buf.toByteArray();
    }

    private String format(final Account account, final ContactInformation info)
            throws ContactInformationStoreException {
        Timestamp on = account.getContactFiledOn();
        if (on == null) {
            on = TimeUtil.nowTs();
        }

        final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        df.setTimeZone(UTC);

        final StringBuilder b = new StringBuilder();
        field(b, "Account-Id", account.getId().toString());
        field(b, "Date", df.format(on) + " " + UTC.getID());
        field(b, "Full-Name", account.getFullName());
        field(b, "Preferred-Email", account.getPreferredEmail());

        try {
            final ReviewDb db = schema.open();
            try {
                for (final AccountExternalId e : db.accountExternalIds().byAccount(account.getId())) {
                    final StringBuilder oistr = new StringBuilder();
                    if (e.getEmailAddress() != null && e.getEmailAddress().length() > 0) {
                        if (oistr.length() > 0) {
                            oistr.append(' ');
                        }
                        oistr.append(e.getEmailAddress());
                    }
                    if (e.isScheme(AccountExternalId.SCHEME_MAILTO)) {
                        if (oistr.length() > 0) {
                            oistr.append(' ');
                        }
                        oistr.append('<');
                        oistr.append(e.getExternalId());
                        oistr.append('>');
                    }
                    field(b, "Identity", oistr.toString());
                }
            } finally {
                db.close();
            }
        } catch (OrmException e) {
            throw new ContactInformationStoreException(e);
        }

        field(b, "Address", info.getAddress());
        field(b, "Country", info.getCountry());
        field(b, "Phone-Number", info.getPhoneNumber());
        field(b, "Fax-Number", info.getFaxNumber());
        return b.toString();
    }

    private static void field(final StringBuilder b, final String name, String value) {
        if (value == null) {
            return;
        }
        value = value.trim();
        if (value.length() == 0) {
            return;
        }

        b.append(name);
        b.append(':');
        if (value.indexOf('\n') == -1) {
            b.append(' ');
            b.append(value);
        } else {
            value = value.replaceAll("\r\n", "\n");
            value = value.replaceAll("\n", "\n\t");
            b.append("\n\t");
            b.append(value);
        }
        b.append('\n');
    }
}