/* * oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. * * Copyright (c) 2014, Gluu */ package org.xdi.oxauth.service.fido.u2f; import java.io.IOException; import java.security.cert.CertificateException; import javax.ejb.Stateless; import javax.inject.Inject; import javax.inject.Named; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.xdi.oxauth.crypto.cert.CertificateParser; import org.xdi.oxauth.crypto.signature.SHA256withECDSASignatureVerification; import org.xdi.oxauth.model.exception.SignatureException; import org.xdi.oxauth.model.fido.u2f.DeviceRegistration; import org.xdi.oxauth.model.fido.u2f.exception.BadInputException; import org.xdi.oxauth.model.fido.u2f.message.RawRegisterResponse; import org.xdi.oxauth.model.fido.u2f.protocol.ClientData; import org.xdi.oxauth.model.util.Base64Util; import org.xdi.util.io.ByteDataInputStream; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; /** * Provides operations with U2F RAW registration response * * @author Yuriy Movchan Date: 05/20/2015 */ @Stateless @Named public class RawRegistrationService { @Inject private Logger log; public static final byte REGISTRATION_RESERVED_BYTE_VALUE = (byte) 0x05; public static final byte REGISTRATION_SIGNED_RESERVED_BYTE_VALUE = (byte) 0x00; public static final long INITIAL_DEVICE_COUNTER_VALUE = -1; public static final String REGISTER_FINISH_TYPE = "navigator.id.finishEnrollment"; public static final String REGISTER_CANCEL_TYPE = "navigator.id.cancelEnrollment"; public static final String[] SUPPORTED_REGISTER_TYPES = new String[] { REGISTER_FINISH_TYPE, REGISTER_CANCEL_TYPE }; @Inject @Named("sha256withECDSASignatureVerification") private SHA256withECDSASignatureVerification signatureVerification; public RawRegisterResponse parseRawRegisterResponse(String rawDataBase64) throws BadInputException { ByteDataInputStream bis = new ByteDataInputStream(Base64Util.base64urldecode(rawDataBase64)); try { try { byte reservedByte = bis.readSigned(); if (reservedByte != REGISTRATION_RESERVED_BYTE_VALUE) { throw new BadInputException("Incorrect value of reserved byte. Expected: " + REGISTRATION_RESERVED_BYTE_VALUE + ". Was: " + reservedByte); } return new RawRegisterResponse(bis.read(65), bis.read(bis.readUnsigned()), CertificateParser.parseDer(bis), bis.readAll()); } catch (IOException ex) { throw new BadInputException("Failed to parse RAW register response", ex); } catch (CertificateException e) { throw new BadInputException("Malformed attestation certificate", e); } } finally { IOUtils.closeQuietly(bis); } } public void checkSignature(String appId, ClientData clientData, RawRegisterResponse rawRegisterResponse) throws BadInputException { String rawClientData = clientData.getRawClientData(); byte[] signedBytes = packBytesToSign(signatureVerification.hash(appId), signatureVerification.hash(rawClientData), rawRegisterResponse.getKeyHandle(), rawRegisterResponse.getUserPublicKey()); try { signatureVerification.checkSignature(rawRegisterResponse.getAttestationCertificate(), signedBytes, rawRegisterResponse.getSignature()); } catch (SignatureException ex) { throw new BadInputException("Failed to checkSignature", ex); } } private byte[] packBytesToSign(byte[] appIdHash, byte[] clientDataHash, byte[] keyHandle, byte[] userPublicKey) { ByteArrayDataOutput encoded = ByteStreams.newDataOutput(); encoded.write(REGISTRATION_SIGNED_RESERVED_BYTE_VALUE); encoded.write(appIdHash); encoded.write(clientDataHash); encoded.write(keyHandle); encoded.write(userPublicKey); return encoded.toByteArray(); } public DeviceRegistration createDevice(RawRegisterResponse rawRegisterResponse) throws BadInputException { return new DeviceRegistration(Base64Util.base64urlencode(rawRegisterResponse.getKeyHandle()), Base64Util.base64urlencode(rawRegisterResponse .getUserPublicKey()), rawRegisterResponse.getAttestationCertificate(), INITIAL_DEVICE_COUNTER_VALUE); } }