/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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.wildfly.security.util; import static org.wildfly.security._private.ElytronMessages.log; import static org.wildfly.security.util.Alphabet.*; import java.util.NoSuchElementException; /** * Non-public numeric iterator base class. It is important to keep this non-public to prevent type confusion between * byte and code point iterators, which are fundamentally incompatible. * * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ abstract class NumericIterator { public abstract boolean hasNext(); public abstract boolean hasPrev(); public abstract int next() throws NoSuchElementException; public abstract int peekNext() throws NoSuchElementException; public abstract int prev() throws NoSuchElementException; public abstract int peekPrev() throws NoSuchElementException; abstract class Base64ByteIterator extends ByteIterator { private final boolean requirePadding; // states: // 0: nothing read // 1: three bytes to return o0..2 // 2: two bytes to return o1..2 (o0 still populated) // 3: one byte to return o2 (o0..o1 still populated) // 4: two bytes then eof o0..1 = // 5: one bytes then eof o1 = (o0 still populated) // 6: one byte then eof o0 == // 7: two bytes then eof o0..1 no pad // 8: one byte then eof o1 no pad (o0 still populated) // 9: one byte then eof o0 no pad // a: end (==) (o0 still populated) // b: end (=) (o0..o1 still populated) // c: end (== but no pad) (o0 still populated) // d: end (= but no pad) (o0..o1 still populated) private int state = 0; private int o0, o1, o2; private int offset; protected Base64ByteIterator(final boolean requirePadding) { this.requirePadding = requirePadding; } public boolean hasNext() { if (state == 0) { if (! NumericIterator.this.hasNext()) { return false; } int b0 = NumericIterator.this.next(); if (b0 == '=') { throw log.unexpectedPadding(); } if (! NumericIterator.this.hasNext()) { if (requirePadding) { throw log.expectedPadding(); } else { throw log.incompleteDecode(); } } int b1 = NumericIterator.this.next(); if (b1 == '=') { throw log.unexpectedPadding(); } o0 = calc0(b0, b1); if (! NumericIterator.this.hasNext()) { if (requirePadding) { log.expectedPadding(); } state = 9; return true; } int b2 = NumericIterator.this.next(); if (b2 == '=') { if (! NumericIterator.this.hasNext()) { throw log.expectedTwoPaddingCharacters(); } if (NumericIterator.this.next() != '=') { throw log.expectedTwoPaddingCharacters(); } state = 6; return true; } o1 = calc1(b1, b2); if (! NumericIterator.this.hasNext()) { if (requirePadding) { log.expectedPadding(); } state = 7; return true; } int b3 = NumericIterator.this.next(); if (b3 == '=') { state = 4; return true; } o2 = calc2(b2, b3); state = 1; return true; } else { return state < 0xa; } } public boolean hasPrev() { return state != 0 || offset > 0; } abstract int calc0(int b0, int b1); abstract int calc1(int b1, int b2); abstract int calc2(int b2, int b3); public int next() { if (! hasNext()) { throw new NoSuchElementException(); } switch (state) { case 1: { state = 2; offset ++; return o0; } case 2: { state = 3; offset ++; return o1; } case 3: { state = 0; offset ++; return o2; } case 4: { state = 5; offset ++; return o0; } case 5: { state = 0xb; offset ++; return o1; } case 6: { state = 0xa; offset ++; return o0; } case 7: { state = 8; offset ++; return o0; } case 8: { state = 0xd; offset ++; return o1; } case 9: { state = 0xc; offset ++; return o0; } default: { // padding throw new NoSuchElementException(); } } } public int peekNext() throws NoSuchElementException { if (! hasNext()) { throw new NoSuchElementException(); } switch (state) { case 1: case 4: case 6: case 7: case 9: { return o0; } case 2: case 5: case 8: { return o1; } case 3: { return o2; } default: { // padding throw new NoSuchElementException(); } } } public int prev() { if (! hasPrev()) { throw new NoSuchElementException(); } switch (state) { case 6: { NumericIterator.this.prev(); // skip = // fall thru } case 4: { NumericIterator.this.prev(); // skip = // fall thru } case 0: case 1: case 7: case 9: { int b3 = NumericIterator.this.prev(); int b2 = NumericIterator.this.prev(); int b1 = NumericIterator.this.prev(); int b0 = NumericIterator.this.prev(); o0 = calc0(b0, b1); o1 = calc1(b1, b2); state = 3; offset --; return o2 = calc2(b2, b3); } case 2: { state = 1; offset --; return o0; } case 3: { state = 2; offset --; return o1; } case 5: { state = 4; offset --; return o0; } case 8: { state = 7; offset --; return o0; } case 0xa: { state = 6; offset --; return o0; } case 0xb: { state = 5; offset --; return o1; } case 0xc: { state = 9; offset --; return o0; } case 0xd: { state = 8; offset --; return o1; } default: { // padding throw new NoSuchElementException(); } } } public int peekPrev() throws NoSuchElementException { if (! hasPrev()) { throw new NoSuchElementException(); } switch (state) { case 6: { NumericIterator.this.prev(); // skip = // fall thru } case 4: { NumericIterator.this.prev(); // skip = // fall thru } case 0: case 1: case 7: case 9: { int b3 = NumericIterator.this.prev(); int b2 = NumericIterator.this.peekPrev(); NumericIterator.this.next(); if (state == 4) { NumericIterator.this.next(); } else if (state == 6) { NumericIterator.this.next(); NumericIterator.this.next(); } return calc2(b2, b3); } case 2: { return o0; } case 3: { return o1; } case 5: { return o0; } case 8: { return o0; } case 0xa: { return o0; } case 0xb: { return o1; } case 0xc: { return o0; } case 0xd: { return o1; } default: { // padding throw new NoSuchElementException(); } } } public int offset() { return offset; } } abstract class Base32ByteIterator extends ByteIterator { private final boolean requirePadding; // states: // 0x00: nothing read // 0x01: five bytes to return o0..o4 // 0x02: four bytes to return o1..o4 (o0 still populated) // 0x03: three byte to return o2..o4 (o0..o1 still populated) // 0x04: two bytes to return o3..o4 (o0..o2 still populated) // 0x05: one byte to return o4 (o0..o3 still populated) // 0x06: four bytes then eof o0..o3 = // 0x07: three bytes then eof o1..o3 = (o0 still populated) // 0x08: two bytes then eof o2..o3 = (o0..o1 still populated) // 0x09: one byte then eof o3 = (o0..o2 still populated) // 0x0a: three bytes then eof o0..o2 === // 0x0b: two bytes then eof o1..o2 === (o0 still populated) // 0x0c: one byte then eof o2 === (o0..o1 still populated) // 0x0d: two bytes then eof o0..o1 ==== // 0x0e: one byte then eof o1 ==== (o0 still populated) // 0x0f: one byte then eof o0 ====== // 0x10: four bytes then eof o0..o3 no pad // 0x11: three bytes then eof o1..o3 no pad (o0 still populated) // 0x12: two bytes then eof o2..o3 no pad (o0..o1 still populated) // 0x13: one byte then eof o3 no pad (o0..o2 still populated) // 0x14: three bytes then eof o0..o2 no pad // 0x15: two bytes then eof o1..o2 no pad (o0 still populated) // 0x16: one byte then eof o2 no pad (o0..o1 still populated) // 0x17: two bytes then eof o0..o1 no pad // 0x18: one byte then eof o1 no pad (o0 still populated) // 0x19: one byte then eof o0 no pad // 0x1a: end (=) (o0..o3 still populated) // 0x1b: end (===) (o0..o2 still populated) // 0x1c: end (====) (o0..o1 still populated) // 0x1d: end (======) (o0 still populated) // 0x1e: end (= but no pad) (o0..o3 still populated) // 0x1f: end (=== but no pad) (o0..o2 still populated) // 0x20: end (==== but no pad) (o0..o1 still populated) // 0x21: end (====== but no pad) (o0 still populated) private int state = 0; private int o0, o1, o2, o3, o4; private int offset; protected Base32ByteIterator(final boolean requirePadding) { this.requirePadding = requirePadding; } public boolean hasNext() { if (state == 0) { if (! NumericIterator.this.hasNext()) { return false; } int b0 = NumericIterator.this.next(); if (b0 == '=') { throw log.unexpectedPadding(); } if (! NumericIterator.this.hasNext()) { if (requirePadding) { throw log.expectedPadding(); } else { throw log.incompleteDecode(); } } int b1 = NumericIterator.this.next(); if (b1 == '=') { throw log.unexpectedPadding(); } o0 = calc0(b0, b1); if (! NumericIterator.this.hasNext()) { if (requirePadding) { throw log.expectedPadding(); } state = 0x19; return true; } int b2 = NumericIterator.this.next(); if (b2 == '=') { for (int i = 0; i < 5; i++) { if (! NumericIterator.this.hasNext()) { throw log.expectedPaddingCharacters(6); } if (NumericIterator.this.next() != '=') { throw log.expectedPaddingCharacters(6); } } state = 0x0f; return true; } if (! NumericIterator.this.hasNext()) { if (requirePadding) { throw log.expectedPadding(); } else { throw log.incompleteDecode(); } } int b3 = NumericIterator.this.next(); if (b3 == '=') { throw log.unexpectedPadding(); } o1 = calc1(b1, b2, b3); if (! NumericIterator.this.hasNext()) { if (requirePadding) { throw log.expectedPadding(); } state = 0x17; return true; } int b4 = NumericIterator.this.next(); if (b4 == '=') { for (int i = 0; i < 3; i++) { if (! NumericIterator.this.hasNext()) { throw log.expectedPaddingCharacters(4); } if (NumericIterator.this.next() != '=') { throw log.expectedPaddingCharacters(4); } } state = 0x0d; return true; } o2 = calc2(b3, b4); if (! NumericIterator.this.hasNext()) { if (requirePadding) { throw log.expectedPadding(); } state = 0x14; return true; } int b5 = NumericIterator.this.next(); if (b5 == '=') { for (int i = 0; i < 2; i++) { if (! NumericIterator.this.hasNext()) { throw log.expectedPaddingCharacters(3); } if (NumericIterator.this.next() != '=') { throw log.expectedPaddingCharacters(3); } } state = 0x0a; return true; } if (! NumericIterator.this.hasNext()) { if (requirePadding) { throw log.expectedPadding(); } else { throw log.incompleteDecode(); } } int b6 = NumericIterator.this.next(); if (b6 == '=') { throw log.unexpectedPadding(); } o3 = calc3(b4, b5, b6); if (! NumericIterator.this.hasNext()) { if (requirePadding) { throw log.expectedPadding(); } state = 0x10; return true; } int b7 = NumericIterator.this.next(); if (b7 == '=') { state = 0x06; return true; } o4 = calc4(b6, b7); state = 1; return true; } else { return state < 0x1a; } } public boolean hasPrev() { return offset > 0; } abstract int calc0(int b0, int b1); abstract int calc1(int b1, int b2, int b3); abstract int calc2(int b3, int b4); abstract int calc3(int b4, int b5, int b6); abstract int calc4(int b6, int b7); public int next() { if (! hasNext()) { throw new NoSuchElementException(); } switch (state) { case 1: case 6: case 0x0a: case 0x0d: case 0x10: case 0x14: case 0x17: { state ++; offset ++; return o0; } case 2: case 7: case 0x0b: case 0x11: case 0x15: { state ++; offset ++; return o1; } case 3: case 8: case 0x12: { state ++; offset ++; return o2; } case 4: { state = 5; offset ++; return o3; } case 5: { state = 0; offset ++; return o4; } case 9: { state = 0x1a; offset ++; return o3; } case 0x0c: { state = 0x1b; offset ++; return o2; } case 0x0e: { state = 0x1c; offset ++; return o1; } case 0x0f: { state = 0x1d; offset ++; return o0; } case 0x13: { state = 0x1e; offset ++; return o3; } case 0x16: { state = 0x1f; offset ++; return o2; } case 0x18: { state = 0x20; offset ++; return o1; } case 0x19: { state = 0x21; offset ++; return o0; } default: { // padding throw new NoSuchElementException(); } } } public int peekNext() throws NoSuchElementException { if (! hasNext()) { throw new NoSuchElementException(); } switch (state) { case 1: case 6: case 0x0a: case 0x0d: case 0x0f: case 0x10: case 0x14: case 0x17: case 0x19: { return o0; } case 2: case 7: case 0x0b: case 0x0e: case 0x11: case 0x15: case 0x18: { return o1; } case 3: case 8: case 0x0c: case 0x12: case 0x16: { return o2; } case 4: case 9: case 0x13: { return o3; } case 5: { return o4; } default: { // padding throw new NoSuchElementException(); } } } public int prev() { if (! hasPrev()) { throw new NoSuchElementException(); } int skipChars = 0; switch (state) { case 0: case 1: case 6: case 0x0a: case 0x0d: case 0x0f: case 0x10: case 0x14: case 0x17: case 0x19: { if (state == 6 || state == 0x0a || state == 0x0d || state == 0x0f) { skipChars = 8; } else if (state == 0x10) { skipChars = 7; } else if (state == 0x14) { skipChars = 5; } else if (state == 0x17) { skipChars = 4; } else if (state == 0x19) { skipChars = 2; } for (int i = 0; i < skipChars; i++) { NumericIterator.this.prev(); // consume character } int b7 = NumericIterator.this.prev(); int b6 = NumericIterator.this.prev(); int b5 = NumericIterator.this.prev(); int b4 = NumericIterator.this.prev(); int b3 = NumericIterator.this.prev(); int b2 = NumericIterator.this.prev(); int b1 = NumericIterator.this.prev(); int b0 = NumericIterator.this.prev(); o0 = calc0(b0, b1); o1 = calc1(b1, b2, b3); o2 = calc2(b3, b4); o3 = calc3(b4, b5, b6); o4 = calc4(b6, b7); state = 5; offset --; return o4; } case 2: case 7: case 0x0b: case 0x0e: case 0x11: case 0x15: case 0x18: { state --; offset --; return o0; } case 3: case 8: case 0x0c: case 0x12: case 0x16: { state --; offset --; return o1; } case 4: case 9: case 0x13: { state --; offset --; return o2; } case 5: { state = 4; offset --; return o3; } case 0x1a: { state = 9; offset --; return o3; } case 0x1b: { state = 0x0c; offset --; return o2; } case 0x1c: { state = 0x0e; offset --; return o1; } case 0x1d: { state = 0x0f; offset --; return o0; } case 0x1e: { state = 0x13; offset --; return o3; } case 0x1f: { state = 0x16; offset --; return o2; } case 0x20: { state = 0x18; offset --; return o1; } case 0x21: { state = 0x19; offset --; return o0; } default: { throw new NoSuchElementException(); } } } public int peekPrev() throws NoSuchElementException { if (! hasPrev()) { throw new NoSuchElementException(); } int skipChars = 0; switch (state) { case 0: case 1: case 6: case 0x0a: case 0x0d: case 0x0f: case 0x10: case 0x14: case 0x17: case 0x19: { if (state == 6 || state == 0x0a || state == 0x0d || state == 0x0f) { skipChars = 8; } else if (state == 0x10) { skipChars = 7; } else if (state == 0x14) { skipChars = 5; } else if (state == 0x17) { skipChars = 4; } else if (state == 0x19) { skipChars = 2; } for (int i = 0; i < skipChars; i++) { NumericIterator.this.prev(); // consume character } int b7 = NumericIterator.this.prev(); int b6 = NumericIterator.this.peekPrev(); NumericIterator.this.next(); for (int i = 0; i < skipChars; i++) { NumericIterator.this.next(); } return calc4(b6, b7); } case 2: case 7: case 0x0b: case 0x0e: case 0x11: case 0x15: case 0x18: case 0x1d: case 0x21: { return o0; } case 3: case 8: case 0x0c: case 0x12: case 0x16: case 0x1c: case 0x20: { return o1; } case 4: case 9: case 0x13: case 0x1b: case 0x1f: { return o2; } case 5: case 0x1a: case 0x1e: { return o3; } default: { throw new NoSuchElementException(); } } } public int offset() { return offset; } } public ByteIterator base64Decode(final Base64Alphabet alphabet, boolean requirePadding) { if (! hasNext()) return ByteIterator.EMPTY; if (alphabet.littleEndian) { return this.new Base64ByteIterator(requirePadding) { int calc0(final int b0, final int b1) { final int d0 = alphabet.decode(b0); final int d1 = alphabet.decode(b1); // d0 = r0[5..0] // d1 = r1[3..0] + r0[7..6] if (d0 == -1 || d1 == -1) throw log.invalidBase64Character(); return (d0 | d1 << 6) & 0xff; } int calc1(final int b1, final int b2) { final int d1 = alphabet.decode(b1); final int d2 = alphabet.decode(b2); // d1 = r1[3..0] + r0[7..6] // d2 = r2[1..0] + r1[7..4] if (d1 == -1 || d2 == -1) throw log.invalidBase64Character(); return (d1 >> 2 | d2 << 4) & 0xff; } int calc2(final int b2, final int b3) { final int d2 = alphabet.decode(b2); final int d3 = alphabet.decode(b3); // d2 = r2[1..0] + r1[7..4] // d3 = r2[7..2] if (d2 == -1 || d3 == -1) throw log.invalidBase64Character(); return (d2 >> 4 | d3 << 2) & 0xff; } }; } else { return this.new Base64ByteIterator(requirePadding) { int calc0(final int b0, final int b1) { final int d0 = alphabet.decode(b0); final int d1 = alphabet.decode(b1); // d0 = r0[7..2] // d1 = r0[1..0] + r1[7..4] if (d0 == -1 || d1 == -1) throw log.invalidBase64Character(); return (d0 << 2 | d1 >> 4) & 0xff; } int calc1(final int b1, final int b2) { final int d1 = alphabet.decode(b1); final int d2 = alphabet.decode(b2); // d1 = r0[1..0] + r1[7..4] // d2 = r1[3..0] + r2[7..6] if (d1 == -1 || d2 == -1) throw log.invalidBase64Character(); return (d1 << 4 | d2 >> 2) & 0xff; } int calc2(final int b2, final int b3) { final int d2 = alphabet.decode(b2); final int d3 = alphabet.decode(b3); // d2 = r1[3..0] + r2[7..6] // d3 = r2[5..0] if (d2 == -1 || d3 == -1) throw log.invalidBase64Character(); return (d2 << 6 | d3) & 0xff; } }; } } public ByteIterator base32Decode(final Base32Alphabet alphabet, boolean requirePadding) { if (! hasNext()) return ByteIterator.EMPTY; if (alphabet.littleEndian) { return this.new Base32ByteIterator(requirePadding) { int calc0(final int b0, final int b1) { final int d0 = alphabet.decode(b0); final int d1 = alphabet.decode(b1); // d0 = r0[4..0] // d1 = r1[1..0] + r0[7..5] if (d0 == -1 || d1 == -1) throw log.invalidBase32Character(); return (d0 | d1 << 5) & 0xff; } int calc1(final int b1, final int b2, final int b3) { final int d1 = alphabet.decode(b1); final int d2 = alphabet.decode(b2); final int d3 = alphabet.decode(b3); // d1 = r1[1..0] + r0[7..5] // d2 = r1[6..2] // d3 = r2[3..0] + r1[7] if (d1 == -1 || d2 == -1 || d3 == -1) throw log.invalidBase32Character(); return (d1 >> 3 | d2 << 2 | d3 << 7) & 0xff; } int calc2(final int b3, final int b4) { final int d3 = alphabet.decode(b3); final int d4 = alphabet.decode(b4); // d3 = r2[3..0] + r1[7] // d4 = r3[0] + r2[7..4] if (d3 == -1 || d4 == -1) throw log.invalidBase32Character(); return (d3 >> 1 | d4 << 4) & 0xff; } int calc3(final int b4, final int b5, final int b6) { final int d4 = alphabet.decode(b4); final int d5 = alphabet.decode(b5); final int d6 = alphabet.decode(b6); // d4 = r3[0] + r2[7..4] // d5 = r3[5..1] // d6 = r4[2..0] + r3[7..6] if (d4 == -1 || d5 == -1 || d6 == -1) throw log.invalidBase32Character(); return (d4 >> 4 | d5 << 1 | d6 << 6) & 0xff; } int calc4(final int b6, final int b7) { final int d6 = alphabet.decode(b6); final int d7 = alphabet.decode(b7); // d6 = r4[2..0] + r3[7..6] // d7 = r4[7..3] if (d6 == -1 || d7 == -1) throw log.invalidBase32Character(); return (d6 >> 2 | d7 << 3) & 0xff; } }; } else { return this.new Base32ByteIterator(requirePadding) { int calc0(final int b0, final int b1) { final int d0 = alphabet.decode(b0); final int d1 = alphabet.decode(b1); // d0 = r0[7..3] // d1 = r0[2..0] + r1[7..6] if (d0 == -1 || d1 == -1) throw log.invalidBase32Character(); return (d0 << 3 | d1 >> 2) & 0xff; } int calc1(final int b1, final int b2, final int b3) { final int d1 = alphabet.decode(b1); final int d2 = alphabet.decode(b2); final int d3 = alphabet.decode(b3); // d1 = r0[2..0] + r1[7..6] // d2 = r1[5..1] // d3 = r1[0] + r2[7..4] if (d1 == -1 || d2 == -1 || d3 == -1) throw log.invalidBase32Character(); return (d1 << 6 | d2 << 1 | d3 >> 4) & 0xff; } int calc2(final int b3, final int b4) { final int d3 = alphabet.decode(b3); final int d4 = alphabet.decode(b4); // d3 = r1[0] + r2[7..4] // d4 = r2[3..0] + r3[7] if (d3 == -1 || d4 == -1) throw log.invalidBase32Character(); return (d3 << 4 | d4 >> 1) & 0xff; } int calc3(final int b4, final int b5, final int b6) { final int d4 = alphabet.decode(b4); final int d5 = alphabet.decode(b5); final int d6 = alphabet.decode(b6); // d4 = r2[3..0] + r3[7] // d5 = r3[6..2] // d6 = r3[1..0] + r4[7..5] if (d4 == -1 || d5 == -1 || d6 == -1) throw log.invalidBase32Character(); return (d4 << 7 | d5 << 2 | d6 >> 3) & 0xff; } int calc4(final int b6, final int b7) { final int d6 = alphabet.decode(b6); final int d7 = alphabet.decode(b7); // d6 = r3[1..0] + r4[7..5] // d7 = r4[4..0] if (d6 == -1 || d7 == -1) throw log.invalidBase32Character(); return (d6 << 5 | d7) & 0xff; } }; } } public ByteIterator hexDecode() { if (! hasNext()) return ByteIterator.EMPTY; return new ByteIterator() { private int b; private int offset; private boolean havePair; private int calc(final int b0, final int b1) { int d0 = Character.digit(b0, 16); int d1 = Character.digit(b1, 16); if (d0 == -1 || d1 == -1) throw log.invalidHexCharacter(); return ((d0 << 4) | d1) & 0xff; } public boolean hasNext() { if (havePair) { return true; } if (! NumericIterator.this.hasNext()) { return false; } int b0 = NumericIterator.this.next(); if (! NumericIterator.this.hasNext()) { throw log.expectedEvenNumberOfHexCharacters(); } int b1 = NumericIterator.this.next(); b = calc(b0, b1); havePair = true; return true; } public boolean hasPrev() { return offset > 0; } public int next() throws NoSuchElementException { if (! hasNext()) { throw new NoSuchElementException(); } offset ++; havePair = false; return b; } public int peekNext() throws NoSuchElementException { if (! hasNext()) { throw new NoSuchElementException(); } return b; } public int prev() throws NoSuchElementException { if (! hasPrev()) { throw new NoSuchElementException(); } int b1 = NumericIterator.this.prev(); int b0 = NumericIterator.this.prev(); b = calc(b0, b1); offset --; havePair = true; return b; } public int peekPrev() throws NoSuchElementException { if (! hasPrev()) { throw new NoSuchElementException(); } int b1 = NumericIterator.this.prev(); int b0 = NumericIterator.this.peekPrev(); NumericIterator.this.next(); return calc(b0, b1); } public int offset() { return offset; } }; } }