org.psl.fidouaf.core.ops.AuthenticationResponseProcessing.java Source code

Java tutorial

Introduction

Here is the source code for org.psl.fidouaf.core.ops.AuthenticationResponseProcessing.java

Source

/*
 * Copyright 2015 eBay Software Foundation
 *
 * 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 org.psl.fidouaf.core.ops;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.psl.fidouaf.core.crypto.Asn1;
import org.psl.fidouaf.core.crypto.KeyCodec;
import org.psl.fidouaf.core.crypto.NamedCurve;
import org.psl.fidouaf.core.crypto.Notary;
import org.psl.fidouaf.core.crypto.RSA;
import org.psl.fidouaf.core.crypto.SHA;
import org.psl.fidouaf.core.dao.StorageInterface;
import org.psl.fidouaf.core.entity.AuthenticationResponse;
import org.psl.fidouaf.core.entity.AuthenticatorRecord;
import org.psl.fidouaf.core.entity.AuthenticatorSignAssertion;
import org.psl.fidouaf.core.entity.FinalChallengeParams;
import org.psl.fidouaf.core.entity.RegistrationRecord;
import org.psl.fidouaf.core.entity.Version;
import org.psl.fidouaf.core.exceptions.DataRecordNotFoundException;
import org.psl.fidouaf.core.exceptions.ServerDataSignatureNotMatchException;
import org.psl.fidouaf.core.tlv.AlgAndEncodingEnum;
import org.psl.fidouaf.core.tlv.Tag;
import org.psl.fidouaf.core.tlv.Tags;
import org.psl.fidouaf.core.tlv.TagsEnum;
import org.psl.fidouaf.core.tlv.TlvAssertionParser;

import com.google.gson.Gson;

public class AuthenticationResponseProcessing {

    private Logger logger = Logger.getLogger(this.getClass().getName());
    private long serverDataExpiryInMs;
    private Notary notary;
    private Gson gson = new Gson();

    public long getServerDataExpiryInMs() {
        return serverDataExpiryInMs;
    }

    public void setServerDataExpiryInMs(long serverDataExpiryInMs) {
        this.serverDataExpiryInMs = serverDataExpiryInMs;
    }

    public AuthenticationResponseProcessing() {

    }

    public AuthenticationResponseProcessing(long serverDataExpiryInMs, Notary notary) {
        this.setServerDataExpiryInMs(serverDataExpiryInMs);
        this.notary = notary;

    }

    public List<AuthenticatorRecord> verify(AuthenticationResponse response, StorageInterface serverData)
            throws Exception {

        // List<AuthenticatorRecord> records = new
        // ArrayList<AuthenticatorRecord>();
        AuthenticatorRecord[] records = new AuthenticatorRecord[response.getAssertions().size()];

        checkVersion(response.getHeader().getUpv());
        checkServerData(response.getHeader().getServerData(), records);
        System.out.println("Getting FCP Params now...");
        FinalChallengeParams fcp = getFcp(response);
        System.out.println("After getting FCP Params.....");
        System.out.println("FCP: " + fcp.toString());
        checkFcp(fcp);

        for (int i = 0; i < records.length; i++) {
            records[i] = processAssertions(response.getAssertions().get(i), serverData);
        }

        List<AuthenticatorRecord> recordsList = new ArrayList<AuthenticatorRecord>(Arrays.asList(records));

        return recordsList;
    }

    private AuthenticatorRecord processAssertions(AuthenticatorSignAssertion authenticatorSignAssertion,
            StorageInterface storage) throws DataRecordNotFoundException {
        TlvAssertionParser parser = new TlvAssertionParser();
        AuthenticatorRecord authRecord = new AuthenticatorRecord();
        RegistrationRecord registrationRecord = null;

        try {
            Tags tags = parser.parse(authenticatorSignAssertion.assertion);
            authRecord.AAID = new String(tags.getTags().get(TagsEnum.TAG_AAID.id).value);
            authRecord.KeyID = Base64.encodeBase64URLSafeString(tags.getTags().get(TagsEnum.TAG_KEYID.id).value);
            // authRecord.KeyID = new String(
            // tags.getTags().get(TagsEnum.TAG_KEYID.id).value);

            registrationRecord = getRegistrationRecordFrmDB(authRecord, storage);
            Tag signnedData = tags.getTags().get(TagsEnum.TAG_UAFV1_SIGNED_DATA.id);
            Tag signature = tags.getTags().get(TagsEnum.TAG_SIGNATURE.id);
            Tag info = tags.getTags().get(TagsEnum.TAG_ASSERTION_INFO.id);
            AlgAndEncodingEnum algAndEncoding = getAlgAndEncoding(info);
            String pubKey = registrationRecord.PublicKey;
            try {
                if (!verifySignature(signnedData, signature, pubKey, algAndEncoding)) {
                    logger.log(Level.INFO,
                            "Signature verification failed for authenticator: " + authRecord.toString());
                    authRecord.status = "FAILED_SIGNATURE_NOT_VALID";
                    return authRecord;
                }
            } catch (Exception e) {
                logger.log(Level.INFO, "Signature verification failed for authenticator: " + authRecord.toString(),
                        e);
                authRecord.status = "FAILED_SIGNATURE_VERIFICATION";
                return authRecord;
            }
            authRecord.username = registrationRecord.username;
            authRecord.deviceId = registrationRecord.deviceId;
            authRecord.status = "SUCCESS";
            return authRecord;
        } catch (IOException e) {
            logger.log(Level.INFO, "Fail to parse assertion: " + authenticatorSignAssertion.assertion, e);
            authRecord.status = "FAILED_ASSERTION_VERIFICATION";
            return authRecord;
        }
    }

    private AlgAndEncodingEnum getAlgAndEncoding(Tag info) {
        int id = (int) info.value[3] + (int) info.value[4] * 256;
        AlgAndEncodingEnum ret = null;
        AlgAndEncodingEnum[] values = AlgAndEncodingEnum.values();
        for (AlgAndEncodingEnum algAndEncodingEnum : values) {
            if (algAndEncodingEnum.id == id) {
                ret = algAndEncodingEnum;
                break;
            }
        }
        logger.info(" : SignatureAlgAndEncoding : " + ret);
        return ret;
    }

    private boolean verifySignature(Tag signedData, Tag signature, String pubKey, AlgAndEncodingEnum algAndEncoding)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException,
            UnsupportedEncodingException, Exception {

        byte[] dataForSigning = getDataForSigning(signedData);

        logger.info(" : pub          : " + pubKey);
        logger.info(" : dataForSigning : " + Base64.encodeBase64URLSafeString(dataForSigning));
        logger.info(" : signature       : " + Base64.encodeBase64URLSafeString(signature.value));

        // This works
        // return NamedCurve.verify(KeyCodec.getKeyAsRawBytes(pubKey),
        // dataForSigning, Asn1.decodeToBigIntegerArray(signature.value));

        byte[] decodeBase64 = Base64.decodeBase64(pubKey);

        /**
         * decoding public_key one more time to allow it to be passed onto X509
         * function to form a x509 type certificate for further usage (for RSA
         * key data)
         */
        // System.out.println("\nDecoded base 64 public Key: "+ new
        // String(decodeBase64));
        // decodeBase64 = Base64.decodeBase64(decodeBase64);
        System.out
                .println("\ndecoded base 64 public key (2nd time to sent to x509 spec)" + new String(decodeBase64));
        if (algAndEncoding == AlgAndEncodingEnum.UAF_ALG_SIGN_RSASSA_PSS_SHA256_RAW) {
            PublicKey publicKey = KeyCodec.getRSAPublicKey(decodeBase64);
            return RSA.verifyPSS(publicKey, SHA.sha(dataForSigning, "SHA-256"), signature.value);
        } else if (algAndEncoding == AlgAndEncodingEnum.UAF_ALG_SIGN_RSASSA_PSS_SHA256_DER) {
            /** commented below code for IOS (RSA keys) compatibility */
            // PublicKey publicKey = KeyCodec.getRSAPublicKey(new
            // DEROctetString(decodeBase64).getOctets());
            // return RSA.verifyPSS(publicKey, SHA.sha(dataForSigning,
            // "SHA-256"), new DEROctetString(signature.value).getOctets());

            PublicKey publicKey = KeyCodec.getPublicKey(new DEROctetString(decodeBase64).getOctets());
            return RSA.verifySimpleRSA(publicKey, dataForSigning, signature.value);
        } else {
            if (algAndEncoding == AlgAndEncodingEnum.UAF_ALG_SIGN_SECP256K1_ECDSA_SHA256_DER) {
                ECPublicKey decodedPub = (ECPublicKey) KeyCodec.getPubKeyFromCurve(decodeBase64, "secp256k1");
                return NamedCurve.verifyUsingSecp256k1(KeyCodec.getKeyAsRawBytes(decodedPub),
                        SHA.sha(dataForSigning, "SHA-256"), Asn1.decodeToBigIntegerArray(signature.value));
            }
            if (algAndEncoding == AlgAndEncodingEnum.UAF_ALG_SIGN_SECP256R1_ECDSA_SHA256_DER) {
                if (decodeBase64.length > 65) {
                    return NamedCurve.verify(KeyCodec.getKeyAsRawBytes(pubKey), SHA.sha(dataForSigning, "SHA-256"),
                            Asn1.decodeToBigIntegerArray(signature.value));
                } else {
                    ECPublicKey decodedPub = (ECPublicKey) KeyCodec.getPubKeyFromCurve(decodeBase64, "secp256r1");
                    return NamedCurve.verify(KeyCodec.getKeyAsRawBytes(decodedPub),
                            SHA.sha(dataForSigning, "SHA-256"), Asn1.decodeToBigIntegerArray(signature.value));
                }
            }
            if (signature.value.length == 64) {
                ECPublicKey decodedPub = (ECPublicKey) KeyCodec.getPubKeyFromCurve(decodeBase64, "secp256r1");
                return NamedCurve.verify(KeyCodec.getKeyAsRawBytes(decodedPub), SHA.sha(dataForSigning, "SHA-256"),
                        Asn1.transformRawSignature(signature.value));
            } else if (65 == decodeBase64.length
                    && AlgAndEncodingEnum.UAF_ALG_SIGN_SECP256R1_ECDSA_SHA256_DER == algAndEncoding) {
                ECPublicKey decodedPub = (ECPublicKey) KeyCodec.getPubKeyFromCurve(decodeBase64, "secp256r1");
                return NamedCurve.verify(KeyCodec.getKeyAsRawBytes(decodedPub), SHA.sha(dataForSigning, "SHA-256"),
                        Asn1.decodeToBigIntegerArray(signature.value));
            } else {
                return NamedCurve.verify(KeyCodec.getKeyAsRawBytes(pubKey), SHA.sha(dataForSigning, "SHA-256"),
                        Asn1.decodeToBigIntegerArray(signature.value));
            }
        }
    }

    private byte[] getDataForSigning(Tag signedData) throws IOException {
        ByteArrayOutputStream byteout = new ByteArrayOutputStream();
        byteout.write(encodeInt(signedData.id));
        byteout.write(encodeInt(signedData.length));
        byteout.write(signedData.value);
        return byteout.toByteArray();
    }

    private byte[] encodeInt(int id) {

        byte[] bytes = new byte[2];
        bytes[0] = (byte) (id & 0x00ff);
        bytes[1] = (byte) ((id & 0xff00) >> 8);
        return bytes;
    }

    private RegistrationRecord getRegistrationRecordFrmDB(AuthenticatorRecord authRecord,
            StorageInterface serverData) throws IOException, DataRecordNotFoundException {
        return serverData.readRegistrationRecordfromDB(authRecord.toString());
    }

    private FinalChallengeParams getFcp(AuthenticationResponse response) {
        String fcp = new String(Base64.decodeBase64(response.getFcParams().getBytes()));
        return gson.fromJson(fcp, FinalChallengeParams.class);
    }

    private void checkServerData(String serverDataB64, AuthenticatorRecord[] records) throws Exception {
        if (notary == null) {
            return;
        }
        String serverData = new String(Base64.decodeBase64(serverDataB64));
        String[] tokens = serverData.split("\\.");
        String signature, timeStamp, challenge, dataToSign;
        try {
            signature = tokens[0];
            timeStamp = tokens[1];
            challenge = tokens[2];
            dataToSign = timeStamp + "." + challenge;
            if (!notary.verify(dataToSign, signature)) {
                throw new ServerDataSignatureNotMatchException();
            }
        } catch (ServerDataSignatureNotMatchException e) {
            setErrorStatus(records, "INVALID_SERVER_DATA_SIGNATURE_NO_MATCH");
            throw new Exception("Invalid server data - Signature not match");
        } catch (Exception e) {
            setErrorStatus(records, "INVALID_SERVER_DATA_CHECK_FAILED");
            throw new Exception("Server data check failed");
        }

    }

    private void setErrorStatus(AuthenticatorRecord[] records, String status) {
        if (records == null || records.length == 0) {
            return;
        }
        for (AuthenticatorRecord rec : records) {
            if (rec == null) {
                rec = new AuthenticatorRecord();
            }
            rec.status = status;
        }
    }

    private void checkVersion(Version upv) throws Exception {
        if (upv.major == 1 && upv.minor == 0) {
            return;
        } else {
            throw new Exception("Invalid version: " + upv.major + "." + upv.minor);
        }
    }

    private void checkFcp(FinalChallengeParams fcp) {
        // TODO Auto-generated method stub
    }
}