com.spotify.crtauth.Fingerprint.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.crtauth.Fingerprint.java

Source

/*
 * Copyright (c) 2014 Spotify AB
 *
 * 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/license/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.spotify.crtauth;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.hash.Hashing;

import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;

import static com.google.common.base.Preconditions.checkPositionIndex;

/**
 * Instances of this class contains a compact representation of the information needed
 * to identify a specific public key used to to sign a payload using the Signer interface.
 * This piece of information then gets sent from the server to the client to ensure that
 * the right key gets picked if there are many signing keys available.
 *
 * The bytes consists of the 6 first bytes of a SHA-1 hash of the traditional binary
 * representation of an RSA key used by ssh-keygen, a simple length value encoding with a
 * 4 byte big endian  length followed by the value as a binary number first the public
 * exponent followed by the modulus.
 */
public class Fingerprint {

    private static final int FINGERPRINT_LENGTH = 6;
    // The size of a newly generated public part of a 4096 bit key
    private static final int TYPICAL_KEY_SIZE = 535;

    private byte[] bytes;

    // TODO: Move CrtAuthCodec into the same package, and make this constructor package local
    public Fingerprint(byte[] fingerprint) {
        this.bytes = fingerprint;
    }

    /**
     * Calculate a bytes from the provided public key.
     * @param publicKey the RSAPublicKey instance to make this fingerprint match
     */
    public Fingerprint(RSAPublicKey publicKey) {
        byte[] digestBytes = Hashing.sha1().hashBytes(getDerEncoding(publicKey)).asBytes();
        checkPositionIndex(FINGERPRINT_LENGTH, digestBytes.length);
        this.bytes = Arrays.copyOf(digestBytes, FINGERPRINT_LENGTH);
    }

    public byte[] getBytes() {
        return bytes;
    }

    /**
     * Returns true if this Fingerprint matches the public key other
     * @param other the other RSAPublicKey to match
     * @return true if this Fingerprint matches other, else false
     */
    public boolean matches(RSAPublicKey other) {
        return this.equals(new Fingerprint(other));
    }

    private static byte[] getDerEncoding(RSAPublicKey key) {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream(TYPICAL_KEY_SIZE);
        DataOutputStream dataOutput = new DataOutputStream(buffer);
        writeVariableLengthOpaque("ssh-rsa".getBytes(), dataOutput);
        writeVariableLengthOpaque(key.getPublicExponent().toByteArray(), dataOutput);
        writeVariableLengthOpaque(key.getModulus().toByteArray(), dataOutput);
        return buffer.toByteArray();
    }

    @VisibleForTesting
    static void writeVariableLengthOpaque(byte[] opaque, DataOutput byteBuffer) {
        try {
            byteBuffer.writeInt(opaque.length);
            byteBuffer.write(opaque);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        Fingerprint that = (Fingerprint) o;

        return Arrays.equals(bytes, that.bytes);

    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(bytes);
    }
}