Java tutorial
/* * 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 } }