com.facebook.delegatedrecovery.CountersignedRecoveryToken.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.delegatedrecovery.CountersignedRecoveryToken.java

Source

/*
 * Copyright (c) 2016-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant 
 * of patent rights can be found in the PATENTS file in the same directory.
 */
package com.facebook.delegatedrecovery;

import org.bouncycastle.crypto.digests.SHA256Digest;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.SignatureException;
import java.security.interfaces.ECPublicKey;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;

/**
 * Represents a countersigned recovery token.
 */
public class CountersignedRecoveryToken extends RecoveryToken {

    /**
     * Extract the issuer from an encoded token so that the configuration can be
     * fetched.
     * 
     * @param encoded The encoded token
     * @return RFC6454 origin string
     */
    public static String extractIssuer(final String encoded) {
        try {
            final byte[] tmp = Base64.getDecoder().decode(encoded);
            int offset = 19;
            final int issuerLength = tmp[offset] << 8 & 0xFF00 | tmp[offset + 1] & 0xFF;
            offset += 2;
            return new String(Arrays.copyOfRange(tmp, offset, offset + issuerLength), "US-ASCII");
        } catch (final UnsupportedEncodingException e) {
            e.printStackTrace();
            System.err.println("US-ASCII encoding was unsupported. Cannot continue.");
            System.exit(1);
            return null; // unreachable
        }
    }

    /**
     * Construct a CountersignedRecovery token from an encoded string. This will
     * automatically verify the signature, issuer, audience and allowed age of the
     * token. InvalidTokenException is thrown if any of these checks fail. The
     * caller is responsible for checking against a replay cache, if desired.
     * 
     * @param encoded Base64 encoded string of the binary countersigned token
     * @param issuer The RFC-6454 origin of the recovery service
     * @param audience RFC-6454 origin of the your service
     * @param keys The countersigning keys to verify the token
     * @param allowedClockSkewSec How much clock skew to allow in seconds
     * @param binding token binding string to verify against, usually null
     * @throws InvalidTokenException If any of the checks fail.
     * @throws InvalidOriginException If the issuer is invalid
     * @throws SignatureException If the keys are invalid.
     * @throws InvalidKeyException If the keys are invalid.
     */
    public CountersignedRecoveryToken(final String encoded, final String issuer, final String audience,
            final ECPublicKey[] keys, final int allowedClockSkewSec, final byte[] binding)
            throws InvalidTokenException, InvalidOriginException, InvalidKeyException, SignatureException {
        super(encoded);
        if (!this.issuer.equals(issuer)) {
            throw new InvalidTokenException("issuer doesn't match expected");
        }
        if (!this.audience.equals(audience)) {
            throw new InvalidTokenException("audience doesn't match expected");
        }
        if (binding != null && !Arrays.equals(this.binding, binding)) {
            throw new InvalidTokenException("binding doesn't match expected");
        }

        if (!this.isSignatureValid(keys)) {
            throw new InvalidTokenException("token signature didn't verify");
        }

        try {
            final long issuedTime = DelegatedRecoveryUtils.fromISO8601(this.issuedTime).getTime();
            final long now = new Date().getTime();
            final long skew = Math.abs(issuedTime - now);

            if (skew > (allowedClockSkewSec * 1000 /* seconds */)) {
                throw new InvalidTokenException("Issued time for token outside valid clock skew window.");
            }
        } catch (ParseException pe) {
            throw new InvalidTokenException("unparsable issuedTime", pe);
        }

    }

    /**
     * Utility method to quickly get a hex-encoded SHA256 digest of the data field
     * of the countersigned token, which contains the original token.
     * 
     * @return hex encoded string of SHA256 digest
     */
    public String getInnerTokenHash() {
        final SHA256Digest digest = new SHA256Digest();
        final byte[] hash = new byte[digest.getByteLength()];
        digest.update(data, 0, data.length);
        digest.doFinal(hash, 0);
        return DelegatedRecoveryUtils.encodeHex(hash);
    }

    protected void typedSanityCheck() {
        if (type != RecoveryToken.TYPE_COUNTERSIGNED_TOKEN) {
            throw new IllegalArgumentException("illegal token type");
        }
    }
}