package pkgYkneoOath; /* * Copyright (c) 2013 Yubico AB * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.MessageDigest; public class OathObj { public static final byte HMAC_MASK = 0x0f; public static final byte HMAC_SHA1 = 0x01; public static final byte HMAC_SHA256 = 0x02; public static final byte OATH_MASK = (byte) 0xf0; public static final byte HOTP_TYPE = 0x10; public static final byte TOTP_TYPE = 0x20; public static final byte PROP_ALWAYS_INCREASING = 1 << 0; private static final short _0 = 0; private static final byte hmac_buf_size = 64; private static final short NAME_LEN = 64; public static final byte IMF_LEN = 4; public static OathObj firstObject; public static OathObj lastObject; public OathObj nextObject; private byte[] name; private short nameLen; private byte type; private byte digits; private short counter = 0; private byte[] imf; private boolean active = false; private byte[] inner; private byte[] outer; private static MessageDigest sha; private static MessageDigest sha256; private MessageDigest digest; private byte[] lastChal; private short lastOffs; private byte props; private static byte[] scratchBuf; public OathObj() { inner = new byte[hmac_buf_size]; outer = new byte[hmac_buf_size]; if(scratchBuf == null) { scratchBuf = JCSystem.makeTransientByteArray((short) 32, JCSystem.CLEAR_ON_DESELECT); } } public void setKey(byte[] buf, short offs, byte type, short len) { if((type & HMAC_MASK) != HMAC_SHA1 && (type & HMAC_MASK) != HMAC_SHA256) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } if((type & OATH_MASK) != HOTP_TYPE && (type & OATH_MASK) != TOTP_TYPE) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } if(len > hmac_buf_size) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } if((type & HMAC_MASK) == HMAC_SHA1) { if(sha == null) { sha = MessageDigest.getInstance(MessageDigest.ALG_SHA, false); } digest = sha; } else if((type & HMAC_MASK) == HMAC_SHA256) { if(sha256 == null) { sha256 = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); } digest = sha256; } this.type = type; this.counter = 0; Util.arrayFillNonAtomic(inner, _0, hmac_buf_size, (byte) 0x36); Util.arrayFillNonAtomic(outer, _0, hmac_buf_size, (byte) 0x5c); for (short i = 0; i < len; i++, offs++) { inner[i] = (byte) (buf[offs] ^ 0x36); outer[i] = (byte) (buf[offs] ^ 0x5c); } } public void setDigits(byte digits) { this.digits = digits; } public byte getDigits() { return digits; } public byte getType() { return type; } public void setName(byte[] buf, short offs, short len) { if(name == null) { name = new byte[NAME_LEN]; } nameLen = len; Util.arrayCopy(buf, offs, name, _0, len); } public short getName(byte[] buf, short offs) { Util.arrayCopy(name, _0, buf, offs, (short) nameLen); return (short) nameLen; } public short getNameLength() { return (short) nameLen; } public void setProp(byte props) { this.props = props; if((props & PROP_ALWAYS_INCREASING) == PROP_ALWAYS_INCREASING) { if(lastChal == null) { lastChal = new byte[hmac_buf_size]; } else { Util.arrayFillNonAtomic(lastChal, _0, hmac_buf_size, (byte) 0); lastOffs = 0; } } } public void addObject() { if(firstObject == null) { firstObject = lastObject = this; } else if(firstObject == lastObject) { firstObject.nextObject = lastObject = this; } else { lastObject.nextObject = lastObject = this; } } public static OathObj getFreeObject() { OathObj object; for(object = firstObject; object != null; object = object.nextObject) { if(!object.isActive()) { break; } } if(object == null) { object = new OathObj(); object.addObject(); } return object; } public static OathObj findObject(byte[] name, short offs, short len) { OathObj object; for(object = firstObject; object != null; object = object.nextObject) { if(!object.isActive() || len != object.nameLen) { continue; } if(Util.arrayCompare(name, offs, object.name, _0, len) == 0) { break; } } return object; } public short calculate(byte[] chal, short chalOffs, short len, byte[] dest, short destOffs) { byte[] buf = null; if((type & OATH_MASK) == TOTP_TYPE) { if(len > hmac_buf_size || len == 0) { ISOException.throwIt(ISO7816.SW_WRONG_DATA); } if((props & PROP_ALWAYS_INCREASING) == PROP_ALWAYS_INCREASING) { short thisOffs = (short) (hmac_buf_size - len); short i = lastOffs < thisOffs ? lastOffs : thisOffs; for(; i < hmac_buf_size; i++) { if(i < thisOffs) { if(lastChal[i] == 0) { continue; } else { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } } else { break; } } short offs = (short) (i - thisOffs + chalOffs); byte compRes = Util.arrayCompare(chal, offs, lastChal, i, len); if(compRes == -1) { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } lastOffs = thisOffs; Util.arrayCopy(chal, chalOffs, lastChal, thisOffs, len); } buf = chal; } else if((type & OATH_MASK) == HOTP_TYPE) { Util.arrayFillNonAtomic(scratchBuf, _0, (short)8, (byte)0); if(imf == null || (imf[0] == 0 && imf[1] == 0 && imf[2] == 0 && imf[3] == 0)) { Util.setShort(scratchBuf, (short) 6, counter); } else { Util.arrayCopyNonAtomic(imf, _0, scratchBuf, (short)4, IMF_LEN); short carry = 0; short ctr1 = (short) ((counter >>> 8) & 0x00ff); short ctr2 = (short) (counter & 0x00ff); for(byte j = 7; j > 0; j--) { short place = (short) (scratchBuf[j] & 0x00ff); if(j == 7) { place += ctr2; } else if(j == 6) { place += ctr1; } place += carry; carry = (byte) (place >>> 8); scratchBuf[j] = (byte) (place); } } counter++; buf = scratchBuf; chalOffs = 0; len = 8; } else { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } digest.reset(); digest.update(inner, _0, hmac_buf_size); short digestLen = digest.doFinal(buf, chalOffs, len, dest, destOffs); digest.reset(); digest.update(outer, _0, hmac_buf_size); return digest.doFinal(dest, destOffs, digestLen, dest, destOffs); } public short calculateTruncated(byte[] chal, short chalOffs, short len, byte[] dest, short destOffs) { short length = calculate(chal, chalOffs, len, scratchBuf, _0); short offs = (short) (scratchBuf[(short)(length - 1)] & 0xf); dest[destOffs++] = (byte) (scratchBuf[offs++] & 0x7f); dest[destOffs++] = scratchBuf[offs++]; dest[destOffs++] = scratchBuf[offs++]; dest[destOffs++] = scratchBuf[offs++]; return 4; } public short getDigestLength() { return digest.getLength(); } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } public void setImf(byte[] buf, short offs) { if(imf == null) { imf = new byte[IMF_LEN]; } for(byte i = 0; i < IMF_LEN; i++) { imf[i] = buf[offs++]; } } public void clearImf() { if(imf != null) { Util.arrayFillNonAtomic(imf, _0, IMF_LEN, (byte)0); } } }