diff options
Diffstat (limited to 'libraries/spongycastle/pg/src/main/java')
184 files changed, 25345 insertions, 0 deletions
diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/BZip2Constants.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/BZip2Constants.java new file mode 100644 index 000000000..9bfd74e0f --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/BZip2Constants.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +/* + * This package is based on the work done by Keiron Liddle, Aftex Software + * <keiron@aftexsw.com> to whom the Ant project is very grateful for his + * great code. + */ + +package org.spongycastle.apache.bzip2; + +/** + * Base class for both the compress and decompress classes. + * Holds common arrays, and static data. + * + * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> + */ +public interface BZip2Constants { + + int baseBlockSize = 100000; + int MAX_ALPHA_SIZE = 258; + int MAX_CODE_LEN = 23; + int RUNA = 0; + int RUNB = 1; + int N_GROUPS = 6; + int G_SIZE = 50; + int N_ITERS = 4; + int MAX_SELECTORS = (2 + (900000 / G_SIZE)); + int NUM_OVERSHOOT_BYTES = 20; + + int[] rNums = { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, + 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, + 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, + 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, + 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, + 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, + 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, + 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, + 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, + 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, + 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, + 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, + 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, + 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, + 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, + 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, + 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, + 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, + 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, + 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, + 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, + 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, + 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, + 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, + 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, + 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, + 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, + 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, + 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, + 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, + 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, + 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, + 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, + 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, + 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, + 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, + 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, + 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, + 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, + 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, + 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, + 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, + 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, + 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, + 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, + 936, 638 + }; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CBZip2InputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CBZip2InputStream.java new file mode 100644 index 000000000..6609482c9 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CBZip2InputStream.java @@ -0,0 +1,848 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +/* + * This package is based on the work done by Keiron Liddle, Aftex Software + * <keiron@aftexsw.com> to whom the Ant project is very grateful for his + * great code. + */ +package org.spongycastle.apache.bzip2; + +import java.io.InputStream; +import java.io.IOException; + +/** + * An input stream that decompresses from the BZip2 format (with the file + * header chars) to be read as any other stream. + * + * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> + * + * <b>NB:</b> note this class has been modified to read the leading BZ from the + * start of the BZIP2 stream to make it compatible with other PGP programs. + */ +public class CBZip2InputStream extends InputStream implements BZip2Constants { + private static void cadvise() { + System.out.println("CRC Error"); + //throw new CCoruptionError(); + } + +// private static void badBGLengths() { +// cadvise(); +// } +// +// private static void bitStreamEOF() { +// cadvise(); +// } + + private static void compressedStreamEOF() { + cadvise(); + } + + private void makeMaps() { + int i; + nInUse = 0; + for (i = 0; i < 256; i++) { + if (inUse[i]) { + seqToUnseq[nInUse] = (char) i; + unseqToSeq[i] = (char) nInUse; + nInUse++; + } + } + } + + /* + index of the last char in the block, so + the block size == last + 1. + */ + private int last; + + /* + index in zptr[] of original string after sorting. + */ + private int origPtr; + + /* + always: in the range 0 .. 9. + The current block size is 100000 * this number. + */ + private int blockSize100k; + + private boolean blockRandomised; + + private int bsBuff; + private int bsLive; + private CRC mCrc = new CRC(); + + private boolean[] inUse = new boolean[256]; + private int nInUse; + + private char[] seqToUnseq = new char[256]; + private char[] unseqToSeq = new char[256]; + + private char[] selector = new char[MAX_SELECTORS]; + private char[] selectorMtf = new char[MAX_SELECTORS]; + + private int[] tt; + private char[] ll8; + + /* + freq table collected to save a pass over the data + during decompression. + */ + private int[] unzftab = new int[256]; + + private int[][] limit = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int[][] base = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int[][] perm = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int[] minLens = new int[N_GROUPS]; + + private InputStream bsStream; + + private boolean streamEnd = false; + + private int currentChar = -1; + + private static final int START_BLOCK_STATE = 1; + private static final int RAND_PART_A_STATE = 2; + private static final int RAND_PART_B_STATE = 3; + private static final int RAND_PART_C_STATE = 4; + private static final int NO_RAND_PART_A_STATE = 5; + private static final int NO_RAND_PART_B_STATE = 6; + private static final int NO_RAND_PART_C_STATE = 7; + + private int currentState = START_BLOCK_STATE; + + private int storedBlockCRC, storedCombinedCRC; + private int computedBlockCRC, computedCombinedCRC; + + int i2, count, chPrev, ch2; + int i, tPos; + int rNToGo = 0; + int rTPos = 0; + int j2; + char z; + + public CBZip2InputStream(InputStream zStream) + throws IOException + { + ll8 = null; + tt = null; + bsSetStream(zStream); + initialize(); + initBlock(); + setupBlock(); + } + + public int read() { + if (streamEnd) { + return -1; + } else { + int retChar = currentChar; + switch(currentState) { + case START_BLOCK_STATE: + break; + case RAND_PART_A_STATE: + break; + case RAND_PART_B_STATE: + setupRandPartB(); + break; + case RAND_PART_C_STATE: + setupRandPartC(); + break; + case NO_RAND_PART_A_STATE: + break; + case NO_RAND_PART_B_STATE: + setupNoRandPartB(); + break; + case NO_RAND_PART_C_STATE: + setupNoRandPartC(); + break; + default: + break; + } + return retChar; + } + } + + private void initialize() throws IOException { + char magic3, magic4; + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + if (magic3 != 'B' && magic4 != 'Z') + { + throw new IOException("Not a BZIP2 marked stream"); + } + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + if (magic3 != 'h' || magic4 < '1' || magic4 > '9') { + bsFinishedWithStream(); + streamEnd = true; + return; + } + + setDecompressStructureSizes(magic4 - '0'); + computedCombinedCRC = 0; + } + + private void initBlock() { + char magic1, magic2, magic3, magic4; + char magic5, magic6; + magic1 = bsGetUChar(); + magic2 = bsGetUChar(); + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + magic5 = bsGetUChar(); + magic6 = bsGetUChar(); + if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 + && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) { + complete(); + return; + } + + if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 + || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) { + badBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = bsGetInt32(); + + if (bsR(1) == 1) { + blockRandomised = true; + } else { + blockRandomised = false; + } + + // currBlockNo++; + getAndMoveToFrontDecode(); + + mCrc.initialiseCRC(); + currentState = START_BLOCK_STATE; + } + + private void endBlock() { + computedBlockCRC = mCrc.getFinalCRC(); + /* A bad CRC is considered a fatal error. */ + if (storedBlockCRC != computedBlockCRC) { + crcError(); + } + + computedCombinedCRC = (computedCombinedCRC << 1) + | (computedCombinedCRC >>> 31); + computedCombinedCRC ^= computedBlockCRC; + } + + private void complete() { + storedCombinedCRC = bsGetInt32(); + if (storedCombinedCRC != computedCombinedCRC) { + crcError(); + } + + bsFinishedWithStream(); + streamEnd = true; + } + + private static void blockOverrun() { + cadvise(); + } + + private static void badBlockHeader() { + cadvise(); + } + + private static void crcError() { + cadvise(); + } + + private void bsFinishedWithStream() { + try { + if (this.bsStream != null) { + if (this.bsStream != System.in) { + this.bsStream.close(); + this.bsStream = null; + } + } + } catch (IOException ioe) { + //ignore + } + } + + private void bsSetStream(InputStream f) { + bsStream = f; + bsLive = 0; + bsBuff = 0; + } + + private int bsR(int n) { + int v; + while (bsLive < n) { + int zzi; + char thech = 0; + try { + thech = (char) bsStream.read(); + } catch (IOException e) { + compressedStreamEOF(); + } + if (thech == -1) { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + + v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); + bsLive -= n; + return v; + } + + private char bsGetUChar() { + return (char) bsR(8); + } + + private int bsGetint() { + int u = 0; + u = (u << 8) | bsR(8); + u = (u << 8) | bsR(8); + u = (u << 8) | bsR(8); + u = (u << 8) | bsR(8); + return u; + } + + private int bsGetIntVS(int numBits) { + return (int) bsR(numBits); + } + + private int bsGetInt32() { + return (int) bsGetint(); + } + + private void hbCreateDecodeTables(int[] limit, int[] base, + int[] perm, char[] length, + int minLen, int maxLen, int alphaSize) { + int pp, i, j, vec; + + pp = 0; + for (i = minLen; i <= maxLen; i++) { + for (j = 0; j < alphaSize; j++) { + if (length[j] == i) { + perm[pp] = j; + pp++; + } + } + } + + for (i = 0; i < MAX_CODE_LEN; i++) { + base[i] = 0; + } + for (i = 0; i < alphaSize; i++) { + base[length[i] + 1]++; + } + + for (i = 1; i < MAX_CODE_LEN; i++) { + base[i] += base[i - 1]; + } + + for (i = 0; i < MAX_CODE_LEN; i++) { + limit[i] = 0; + } + vec = 0; + + for (i = minLen; i <= maxLen; i++) { + vec += (base[i + 1] - base[i]); + limit[i] = vec - 1; + vec <<= 1; + } + for (i = minLen + 1; i <= maxLen; i++) { + base[i] = ((limit[i - 1] + 1) << 1) - base[i]; + } + } + + private void recvDecodingTables() { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + int i, j, t, nGroups, nSelectors, alphaSize; + int minLen, maxLen; + boolean[] inUse16 = new boolean[16]; + + /* Receive the mapping table */ + for (i = 0; i < 16; i++) { + if (bsR(1) == 1) { + inUse16[i] = true; + } else { + inUse16[i] = false; + } + } + + for (i = 0; i < 256; i++) { + inUse[i] = false; + } + + for (i = 0; i < 16; i++) { + if (inUse16[i]) { + for (j = 0; j < 16; j++) { + if (bsR(1) == 1) { + inUse[i * 16 + j] = true; + } + } + } + } + + makeMaps(); + alphaSize = nInUse + 2; + + /* Now the selectors */ + nGroups = bsR(3); + nSelectors = bsR(15); + for (i = 0; i < nSelectors; i++) { + j = 0; + while (bsR(1) == 1) { + j++; + } + selectorMtf[i] = (char) j; + } + + /* Undo the MTF values for the selectors. */ + { + char[] pos = new char[N_GROUPS]; + char tmp, v; + for (v = 0; v < nGroups; v++) { + pos[v] = v; + } + + for (i = 0; i < nSelectors; i++) { + v = selectorMtf[i]; + tmp = pos[v]; + while (v > 0) { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + } + + /* Now the coding tables */ + for (t = 0; t < nGroups; t++) { + int curr = bsR(5); + for (i = 0; i < alphaSize; i++) { + while (bsR(1) == 1) { + if (bsR(1) == 0) { + curr++; + } else { + curr--; + } + } + len[t][i] = (char) curr; + } + } + + /* Create the Huffman decoding tables */ + for (t = 0; t < nGroups; t++) { + minLen = 32; + maxLen = 0; + for (i = 0; i < alphaSize; i++) { + if (len[t][i] > maxLen) { + maxLen = len[t][i]; + } + if (len[t][i] < minLen) { + minLen = len[t][i]; + } + } + hbCreateDecodeTables(limit[t], base[t], perm[t], len[t], minLen, + maxLen, alphaSize); + minLens[t] = minLen; + } + } + + private void getAndMoveToFrontDecode() { + char[] yy = new char[256]; + int i, j, nextSym, limitLast; + int EOB, groupNo, groupPos; + + limitLast = baseBlockSize * blockSize100k; + origPtr = bsGetIntVS(24); + + recvDecodingTables(); + EOB = nInUse + 1; + groupNo = -1; + groupPos = 0; + + /* + Setting up the unzftab entries here is not strictly + necessary, but it does save having to do it later + in a separate pass, and so saves a block's worth of + cache misses. + */ + for (i = 0; i <= 255; i++) { + unzftab[i] = 0; + } + + for (i = 0; i <= 255; i++) { + yy[i] = (char) i; + } + + last = -1; + + { + int zt, zn, zvec, zj; + if (groupPos == 0) { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR(zn); + while (zvec > limit[zt][zn]) { + zn++; + { + { + while (bsLive < 1) { + int zzi; + char thech = 0; + try { + thech = (char) bsStream.read(); + } catch (IOException e) { + compressedStreamEOF(); + } + if (thech == -1) { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + } + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - base[zt][zn]]; + } + + while (true) { + + if (nextSym == EOB) { + break; + } + + if (nextSym == RUNA || nextSym == RUNB) { + char ch; + int s = -1; + int N = 1; + do { + if (nextSym == RUNA) { + s = s + (0 + 1) * N; + } else if (nextSym == RUNB) { + s = s + (1 + 1) * N; + } + N = N * 2; + { + int zt, zn, zvec, zj; + if (groupPos == 0) { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR(zn); + while (zvec > limit[zt][zn]) { + zn++; + { + { + while (bsLive < 1) { + int zzi; + char thech = 0; + try { + thech = (char) bsStream.read(); + } catch (IOException e) { + compressedStreamEOF(); + } + if (thech == -1) { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + } + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - base[zt][zn]]; + } + } while (nextSym == RUNA || nextSym == RUNB); + + s++; + ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while (s > 0) { + last++; + ll8[last] = ch; + s--; + } + + if (last >= limitLast) { + blockOverrun(); + } + continue; + } else { + char tmp; + last++; + if (last >= limitLast) { + blockOverrun(); + } + + tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + /* + This loop is hammered during decompression, + hence the unrolling. + + for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1]; + */ + + j = nextSym - 1; + for (; j > 3; j -= 4) { + yy[j] = yy[j - 1]; + yy[j - 1] = yy[j - 2]; + yy[j - 2] = yy[j - 3]; + yy[j - 3] = yy[j - 4]; + } + for (; j > 0; j--) { + yy[j] = yy[j - 1]; + } + + yy[0] = tmp; + { + int zt, zn, zvec, zj; + if (groupPos == 0) { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR(zn); + while (zvec > limit[zt][zn]) { + zn++; + { + { + while (bsLive < 1) { + int zzi; + char thech = 0; + try { + thech = (char) bsStream.read(); + } catch (IOException e) { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = (bsBuff << 8) | (zzi & 0xff); + bsLive += 8; + } + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + } + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - base[zt][zn]]; + } + continue; + } + } + } + + private void setupBlock() { + int[] cftab = new int[257]; + char ch; + + cftab[0] = 0; + for (i = 1; i <= 256; i++) { + cftab[i] = unzftab[i - 1]; + } + for (i = 1; i <= 256; i++) { + cftab[i] += cftab[i - 1]; + } + + for (i = 0; i <= last; i++) { + ch = (char) ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + cftab = null; + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; /* not a char and not EOF */ + + if (blockRandomised) { + rNToGo = 0; + rTPos = 0; + setupRandPartA(); + } else { + setupNoRandPartA(); + } + } + + private void setupRandPartA() { + if (i2 <= last) { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) { + rNToGo = rNums[rTPos]; + rTPos++; + if (rTPos == 512) { + rTPos = 0; + } + } + rNToGo--; + ch2 ^= (int) ((rNToGo == 1) ? 1 : 0); + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.updateCRC(ch2); + } else { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupNoRandPartA() { + if (i2 <= last) { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.updateCRC(ch2); + } else { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupRandPartB() { + if (ch2 != chPrev) { + currentState = RAND_PART_A_STATE; + count = 1; + setupRandPartA(); + } else { + count++; + if (count >= 4) { + z = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) { + rNToGo = rNums[rTPos]; + rTPos++; + if (rTPos == 512) { + rTPos = 0; + } + } + rNToGo--; + z ^= ((rNToGo == 1) ? 1 : 0); + j2 = 0; + currentState = RAND_PART_C_STATE; + setupRandPartC(); + } else { + currentState = RAND_PART_A_STATE; + setupRandPartA(); + } + } + } + + private void setupRandPartC() { + if (j2 < (int) z) { + currentChar = ch2; + mCrc.updateCRC(ch2); + j2++; + } else { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + setupRandPartA(); + } + } + + private void setupNoRandPartB() { + if (ch2 != chPrev) { + currentState = NO_RAND_PART_A_STATE; + count = 1; + setupNoRandPartA(); + } else { + count++; + if (count >= 4) { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + setupNoRandPartC(); + } else { + currentState = NO_RAND_PART_A_STATE; + setupNoRandPartA(); + } + } + } + + private void setupNoRandPartC() { + if (j2 < (int) z) { + currentChar = ch2; + mCrc.updateCRC(ch2); + j2++; + } else { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + setupNoRandPartA(); + } + } + + private void setDecompressStructureSizes(int newSize100k) { + if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k + && blockSize100k <= 9)) { + // throw new IOException("Invalid block size"); + } + + blockSize100k = newSize100k; + + if (newSize100k == 0) { + return; + } + + int n = baseBlockSize * newSize100k; + ll8 = new char[n]; + tt = new int[n]; + } +} + diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CBZip2OutputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CBZip2OutputStream.java new file mode 100644 index 000000000..9648663af --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CBZip2OutputStream.java @@ -0,0 +1,1651 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +/* + * This package is based on the work done by Keiron Liddle, Aftex Software + * <keiron@aftexsw.com> to whom the Ant project is very grateful for his + * great code. + */ + +package org.spongycastle.apache.bzip2; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * An output stream that compresses into the BZip2 format (with the file + * header chars) into another stream. + * + * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> + * + * TODO: Update to BZip2 1.0.1 + * <b>NB:</b> note this class has been modified to add a leading BZ to the + * start of the BZIP2 stream to make it compatible with other PGP programs. + */ +public class CBZip2OutputStream extends OutputStream implements BZip2Constants { + protected static final int SETMASK = (1 << 21); + protected static final int CLEARMASK = (~SETMASK); + protected static final int GREATER_ICOST = 15; + protected static final int LESSER_ICOST = 0; + protected static final int SMALL_THRESH = 20; + protected static final int DEPTH_THRESH = 10; + + /* + If you are ever unlucky/improbable enough + to get a stack overflow whilst sorting, + increase the following constant and try + again. In practice I have never seen the + stack go above 27 elems, so the following + limit seems very generous. + */ + protected static final int QSORT_STACK_SIZE = 1000; + private boolean finished; + + private static void panic() { + System.out.println("panic"); + //throw new CError(); + } + + private void makeMaps() { + int i; + nInUse = 0; + for (i = 0; i < 256; i++) { + if (inUse[i]) { + seqToUnseq[nInUse] = (char) i; + unseqToSeq[i] = (char) nInUse; + nInUse++; + } + } + } + + protected static void hbMakeCodeLengths(char[] len, int[] freq, + int alphaSize, int maxLen) { + /* + Nodes and heap entries run from 1. Entry 0 + for both the heap and nodes is a sentinel. + */ + int nNodes, nHeap, n1, n2, i, j, k; + boolean tooLong; + + int[] heap = new int[MAX_ALPHA_SIZE + 2]; + int[] weight = new int[MAX_ALPHA_SIZE * 2]; + int[] parent = new int[MAX_ALPHA_SIZE * 2]; + + for (i = 0; i < alphaSize; i++) { + weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8; + } + + while (true) { + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for (i = 1; i <= alphaSize; i++) { + parent[i] = -1; + nHeap++; + heap[nHeap] = i; + { + int zz, tmp; + zz = nHeap; + tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if (!(nHeap < (MAX_ALPHA_SIZE + 2))) { + panic(); + } + + while (nHeap > 1) { + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0, yy = 0, tmp = 0; + zz = 1; + tmp = heap[zz]; + while (true) { + yy = zz << 1; + if (yy > nHeap) { + break; + } + if (yy < nHeap + && weight[heap[yy + 1]] < weight[heap[yy]]) { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) { + break; + } + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0, yy = 0, tmp = 0; + zz = 1; + tmp = heap[zz]; + while (true) { + yy = zz << 1; + if (yy > nHeap) { + break; + } + if (yy < nHeap + && weight[heap[yy + 1]] < weight[heap[yy]]) { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) { + break; + } + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + nNodes++; + parent[n1] = parent[n2] = nNodes; + + weight[nNodes] = ((weight[n1] & 0xffffff00) + + (weight[n2] & 0xffffff00)) + | (1 + (((weight[n1] & 0x000000ff) > + (weight[n2] & 0x000000ff)) ? + (weight[n1] & 0x000000ff) : + (weight[n2] & 0x000000ff))); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; + { + int zz = 0, tmp = 0; + zz = nHeap; + tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if (!(nNodes < (MAX_ALPHA_SIZE * 2))) { + panic(); + } + + tooLong = false; + for (i = 1; i <= alphaSize; i++) { + j = 0; + k = i; + while (parent[k] >= 0) { + k = parent[k]; + j++; + } + len[i - 1] = (char) j; + if (j > maxLen) { + tooLong = true; + } + } + + if (!tooLong) { + break; + } + + for (i = 1; i < alphaSize; i++) { + j = weight[i] >> 8; + j = 1 + (j / 2); + weight[i] = j << 8; + } + } + } + + /* + index of the last char in the block, so + the block size == last + 1. + */ + int last; + + /* + index in zptr[] of original string after sorting. + */ + int origPtr; + + /* + always: in the range 0 .. 9. + The current block size is 100000 * this number. + */ + int blockSize100k; + + boolean blockRandomised; + + int bytesOut; + int bsBuff; + int bsLive; + CRC mCrc = new CRC(); + + private boolean[] inUse = new boolean[256]; + private int nInUse; + + private char[] seqToUnseq = new char[256]; + private char[] unseqToSeq = new char[256]; + + private char[] selector = new char[MAX_SELECTORS]; + private char[] selectorMtf = new char[MAX_SELECTORS]; + + private char[] block; + private int[] quadrant; + private int[] zptr; + private short[] szptr; + private int[] ftab; + + private int nMTF; + + private int[] mtfFreq = new int[MAX_ALPHA_SIZE]; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private int workFactor; + private int workDone; + private int workLimit; + private boolean firstAttempt; + private int nBlocksRandomised; + + private int currentChar = -1; + private int runLength = 0; + + public CBZip2OutputStream(OutputStream inStream) throws IOException { + this(inStream, 9); + } + + public CBZip2OutputStream(OutputStream inStream, int inBlockSize) + throws IOException { + block = null; + quadrant = null; + zptr = null; + ftab = null; + + inStream.write('B'); + inStream.write('Z'); + + bsSetStream(inStream); + + workFactor = 50; + if (inBlockSize > 9) { + inBlockSize = 9; + } + if (inBlockSize < 1) { + inBlockSize = 1; + } + blockSize100k = inBlockSize; + allocateCompressStructures(); + initialize(); + initBlock(); + } + + /** + * + * modified by Oliver Merkel, 010128 + * + */ + public void write(int bv) throws IOException { + int b = (256 + bv) % 256; + if (currentChar != -1) { + if (currentChar == b) { + runLength++; + if (runLength > 254) { + writeRun(); + currentChar = -1; + runLength = 0; + } + } else { + writeRun(); + runLength = 1; + currentChar = b; + } + } else { + currentChar = b; + runLength++; + } + } + + private void writeRun() throws IOException { + if (last < allowableBlockSize) { + inUse[currentChar] = true; + for (int i = 0; i < runLength; i++) { + mCrc.updateCRC((char) currentChar); + } + switch (runLength) { + case 1: + last++; + block[last + 1] = (char) currentChar; + break; + case 2: + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + break; + case 3: + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + break; + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) currentChar; + last++; + block[last + 1] = (char) (runLength - 4); + break; + } + } else { + endBlock(); + initBlock(); + writeRun(); + } + } + + boolean closed = false; + + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + + public void close() throws IOException { + if (closed) { + return; + } + + finish(); + + closed = true; + super.close(); + bsStream.close(); + } + + public void finish() throws IOException { + if (finished) { + return; + } + + if (runLength > 0) { + writeRun(); + } + currentChar = -1; + endBlock(); + endCompression(); + finished = true; + flush(); + } + + public void flush() throws IOException { + super.flush(); + bsStream.flush(); + } + + private int blockCRC, combinedCRC; + + private void initialize() throws IOException { + bytesOut = 0; + nBlocksRandomised = 0; + + /* Write `magic' bytes h indicating file-format == huffmanised, + followed by a digit indicating blockSize100k. + */ + bsPutUChar('h'); + bsPutUChar('0' + blockSize100k); + + combinedCRC = 0; + } + + private int allowableBlockSize; + + private void initBlock() { + // blockNo++; + mCrc.initialiseCRC(); + last = -1; + // ch = 0; + + for (int i = 0; i < 256; i++) { + inUse[i] = false; + } + + /* 20 is just a paranoia constant */ + allowableBlockSize = baseBlockSize * blockSize100k - 20; + } + + private void endBlock() throws IOException { + blockCRC = mCrc.getFinalCRC(); + combinedCRC = (combinedCRC << 1) | (combinedCRC >>> 31); + combinedCRC ^= blockCRC; + + /* sort the block and establish posn of original string */ + doReversibleTransformation(); + + /* + A 6-byte block header, the value chosen arbitrarily + as 0x314159265359 :-). A 32 bit value does not really + give a strong enough guarantee that the value will not + appear by chance in the compressed datastream. Worst-case + probability of this event, for a 900k block, is about + 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + For a compressed file of size 100Gb -- about 100000 blocks -- + only a 48-bit marker will do. NB: normal compression/ + decompression do *not* rely on these statistical properties. + They are only important when trying to recover blocks from + damaged files. + */ + bsPutUChar(0x31); + bsPutUChar(0x41); + bsPutUChar(0x59); + bsPutUChar(0x26); + bsPutUChar(0x53); + bsPutUChar(0x59); + + /* Now the block's CRC, so it is in a known place. */ + bsPutint(blockCRC); + + /* Now a single bit indicating randomisation. */ + if (blockRandomised) { + bsW(1, 1); + nBlocksRandomised++; + } else { + bsW(1, 0); + } + + /* Finally, block's contents proper. */ + moveToFrontCodeAndSend(); + } + + private void endCompression() throws IOException { + /* + Now another magic 48-bit number, 0x177245385090, to + indicate the end of the last block. (sqrt(pi), if + you want to know. I did want to use e, but it contains + too much repetition -- 27 18 28 18 28 46 -- for me + to feel statistically comfortable. Call me paranoid.) + */ + bsPutUChar(0x17); + bsPutUChar(0x72); + bsPutUChar(0x45); + bsPutUChar(0x38); + bsPutUChar(0x50); + bsPutUChar(0x90); + + bsPutint(combinedCRC); + + bsFinishedWithStream(); + } + + private void hbAssignCodes (int[] code, char[] length, int minLen, + int maxLen, int alphaSize) { + int n, vec, i; + + vec = 0; + for (n = minLen; n <= maxLen; n++) { + for (i = 0; i < alphaSize; i++) { + if (length[i] == n) { + code[i] = vec; + vec++; + } + } + vec <<= 1; + } + } + + private void bsSetStream(OutputStream f) { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + } + + private void bsFinishedWithStream() throws IOException { + while (bsLive > 0) { + int ch = (bsBuff >> 24); + try { + bsStream.write(ch); // write 8-bit + } catch (IOException e) { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void bsW(int n, int v) throws IOException { + while (bsLive >= 8) { + int ch = (bsBuff >> 24); + try { + bsStream.write(ch); // write 8-bit + } catch (IOException e) { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + bsBuff |= (v << (32 - bsLive - n)); + bsLive += n; + } + + private void bsPutUChar(int c) throws IOException { + bsW(8, c); + } + + private void bsPutint(int u) throws IOException { + bsW(8, (u >> 24) & 0xff); + bsW(8, (u >> 16) & 0xff); + bsW(8, (u >> 8) & 0xff); + bsW(8, u & 0xff); + } + + private void bsPutIntVS(int numBits, int c) throws IOException { + bsW(numBits, c); + } + + private void sendMTFValues() throws IOException { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + + int v, t, i, j, gs, ge, totc, bt, bc, iter; + int nSelectors = 0, alphaSize, minLen, maxLen, selCtr; + int nGroups;//, nBytes; + + alphaSize = nInUse + 2; + for (t = 0; t < N_GROUPS; t++) { + for (v = 0; v < alphaSize; v++) { + len[t][v] = (char) GREATER_ICOST; + } + } + + /* Decide how many coding tables to use */ + if (nMTF <= 0) { + panic(); + } + + if (nMTF < 200) { + nGroups = 2; + } else if (nMTF < 600) { + nGroups = 3; + } else if (nMTF < 1200) { + nGroups = 4; + } else if (nMTF < 2400) { + nGroups = 5; + } else { + nGroups = 6; + } + + /* Generate an initial set of coding tables */ { + int nPart, remF, tFreq, aFreq; + + nPart = nGroups; + remF = nMTF; + gs = 0; + while (nPart > 0) { + tFreq = remF / nPart; + ge = gs - 1; + aFreq = 0; + while (aFreq < tFreq && ge < alphaSize - 1) { + ge++; + aFreq += mtfFreq[ge]; + } + + if (ge > gs && nPart != nGroups && nPart != 1 + && ((nGroups - nPart) % 2 == 1)) { + aFreq -= mtfFreq[ge]; + ge--; + } + + for (v = 0; v < alphaSize; v++) { + if (v >= gs && v <= ge) { + len[nPart - 1][v] = (char) LESSER_ICOST; + } else { + len[nPart - 1][v] = (char) GREATER_ICOST; + } + } + + nPart--; + gs = ge + 1; + remF -= aFreq; + } + } + + int[][] rfreq = new int[N_GROUPS][MAX_ALPHA_SIZE]; + int[] fave = new int[N_GROUPS]; + short[] cost = new short[N_GROUPS]; + /* + Iterate up to N_ITERS times to improve the tables. + */ + for (iter = 0; iter < N_ITERS; iter++) { + for (t = 0; t < nGroups; t++) { + fave[t] = 0; + } + + for (t = 0; t < nGroups; t++) { + for (v = 0; v < alphaSize; v++) { + rfreq[t][v] = 0; + } + } + + nSelectors = 0; + totc = 0; + gs = 0; + while (true) { + + /* Set group start & end marks. */ + if (gs >= nMTF) { + break; + } + ge = gs + G_SIZE - 1; + if (ge >= nMTF) { + ge = nMTF - 1; + } + + /* + Calculate the cost of this group as coded + by each of the coding tables. + */ + for (t = 0; t < nGroups; t++) { + cost[t] = 0; + } + + if (nGroups == 6) { + short cost0, cost1, cost2, cost3, cost4, cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for (i = gs; i <= ge; i++) { + short icv = szptr[i]; + cost0 += len[0][icv]; + cost1 += len[1][icv]; + cost2 += len[2][icv]; + cost3 += len[3][icv]; + cost4 += len[4][icv]; + cost5 += len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } else { + for (i = gs; i <= ge; i++) { + short icv = szptr[i]; + for (t = 0; t < nGroups; t++) { + cost[t] += len[t][icv]; + } + } + } + + /* + Find the coding table which is best for this group, + and record its identity in the selector table. + */ + bc = 999999999; + bt = -1; + for (t = 0; t < nGroups; t++) { + if (cost[t] < bc) { + bc = cost[t]; + bt = t; + } + } + totc += bc; + fave[bt]++; + selector[nSelectors] = (char) bt; + nSelectors++; + + /* + Increment the symbol frequencies for the selected table. + */ + for (i = gs; i <= ge; i++) { + rfreq[bt][szptr[i]]++; + } + + gs = ge + 1; + } + + /* + Recompute the tables based on the accumulated frequencies. + */ + for (t = 0; t < nGroups; t++) { + hbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20); + } + } + + rfreq = null; + fave = null; + cost = null; + + if (!(nGroups < 8)) { + panic(); + } + if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / G_SIZE)))) { + panic(); + } + + + /* Compute MTF values for the selectors. */ + { + char[] pos = new char[N_GROUPS]; + char ll_i, tmp2, tmp; + for (i = 0; i < nGroups; i++) { + pos[i] = (char) i; + } + for (i = 0; i < nSelectors; i++) { + ll_i = selector[i]; + j = 0; + tmp = pos[j]; + while (ll_i != tmp) { + j++; + tmp2 = tmp; + tmp = pos[j]; + pos[j] = tmp2; + } + pos[0] = tmp; + selectorMtf[i] = (char) j; + } + } + + int[][] code = new int[N_GROUPS][MAX_ALPHA_SIZE]; + + /* Assign actual codes for the tables. */ + for (t = 0; t < nGroups; t++) { + minLen = 32; + maxLen = 0; + for (i = 0; i < alphaSize; i++) { + if (len[t][i] > maxLen) { + maxLen = len[t][i]; + } + if (len[t][i] < minLen) { + minLen = len[t][i]; + } + } + if (maxLen > 20) { + panic(); + } + if (minLen < 1) { + panic(); + } + hbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize); + } + + /* Transmit the mapping table. */ + { + boolean[] inUse16 = new boolean[16]; + for (i = 0; i < 16; i++) { + inUse16[i] = false; + for (j = 0; j < 16; j++) { + if (inUse[i * 16 + j]) { + inUse16[i] = true; + } + } + } + +// nBytes = bytesOut; + for (i = 0; i < 16; i++) { + if (inUse16[i]) { + bsW(1, 1); + } else { + bsW(1, 0); + } + } + + for (i = 0; i < 16; i++) { + if (inUse16[i]) { + for (j = 0; j < 16; j++) { + if (inUse[i * 16 + j]) { + bsW(1, 1); + } else { + bsW(1, 0); + } + } + } + } + + } + + /* Now the selectors. */ +// nBytes = bytesOut; + bsW (3, nGroups); + bsW (15, nSelectors); + for (i = 0; i < nSelectors; i++) { + for (j = 0; j < selectorMtf[i]; j++) { + bsW(1, 1); + } + bsW(1, 0); + } + + /* Now the coding tables. */ +// nBytes = bytesOut; + + for (t = 0; t < nGroups; t++) { + int curr = len[t][0]; + bsW(5, curr); + for (i = 0; i < alphaSize; i++) { + while (curr < len[t][i]) { + bsW(2, 2); + curr++; /* 10 */ + } + while (curr > len[t][i]) { + bsW(2, 3); + curr--; /* 11 */ + } + bsW (1, 0); + } + } + + /* And finally, the block data proper */ +// nBytes = bytesOut; + selCtr = 0; + gs = 0; + while (true) { + if (gs >= nMTF) { + break; + } + ge = gs + G_SIZE - 1; + if (ge >= nMTF) { + ge = nMTF - 1; + } + for (i = gs; i <= ge; i++) { + bsW(len[selector[selCtr]][szptr[i]], + code[selector[selCtr]][szptr[i]]); + } + + gs = ge + 1; + selCtr++; + } + if (!(selCtr == nSelectors)) { + panic(); + } + } + + private void moveToFrontCodeAndSend () throws IOException { + bsPutIntVS(24, origPtr); + generateMTFValues(); + sendMTFValues(); + } + + private OutputStream bsStream; + + private void simpleSort(int lo, int hi, int d) { + int i, j, h, bigN, hp; + int v; + + bigN = hi - lo + 1; + if (bigN < 2) { + return; + } + + hp = 0; + while (incs[hp] < bigN) { + hp++; + } + hp--; + + for (; hp >= 0; hp--) { + h = incs[hp]; + + i = lo + h; + while (true) { + /* copy 1 */ + if (i > hi) { + break; + } + v = zptr[i]; + j = i; + while (fullGtU(zptr[j - h] + d, v + d)) { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) { + break; + } + } + zptr[j] = v; + i++; + + /* copy 2 */ + if (i > hi) { + break; + } + v = zptr[i]; + j = i; + while (fullGtU(zptr[j - h] + d, v + d)) { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) { + break; + } + } + zptr[j] = v; + i++; + + /* copy 3 */ + if (i > hi) { + break; + } + v = zptr[i]; + j = i; + while (fullGtU(zptr[j - h] + d, v + d)) { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) { + break; + } + } + zptr[j] = v; + i++; + + if (workDone > workLimit && firstAttempt) { + return; + } + } + } + } + + private void vswap(int p1, int p2, int n) { + int temp = 0; + while (n > 0) { + temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } + + private char med3(char a, char b, char c) { + char t; + if (a > b) { + t = a; + a = b; + b = t; + } + if (b > c) { + t = b; + b = c; + c = t; + } + if (a > b) { + b = a; + } + return b; + } + + private static class StackElem { + int ll; + int hh; + int dd; + } + + private void qSort3(int loSt, int hiSt, int dSt) { + int unLo, unHi, ltLo, gtHi, med, n, m; + int sp, lo, hi, d; + StackElem[] stack = new StackElem[QSORT_STACK_SIZE]; + for (int count = 0; count < QSORT_STACK_SIZE; count++) { + stack[count] = new StackElem(); + } + + sp = 0; + + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; + + while (sp > 0) { + if (sp >= QSORT_STACK_SIZE) { + panic(); + } + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) { + simpleSort(lo, hi, d); + if (workDone > workLimit && firstAttempt) { + return; + } + continue; + } + + med = med3(block[zptr[lo] + d + 1], + block[zptr[hi ] + d + 1], + block[zptr[(lo + hi) >> 1] + d + 1]); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while (true) { + while (true) { + if (unLo > unHi) { + break; + } + n = ((int) block[zptr[unLo] + d + 1]) - med; + if (n == 0) { + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + } + if (n > 0) { + break; + } + unLo++; + } + while (true) { + if (unLo > unHi) { + break; + } + n = ((int) block[zptr[unHi] + d + 1]) - med; + if (n == 0) { + int temp = 0; + temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + } + if (n < 0) { + break; + } + unHi--; + } + if (unLo > unHi) { + break; + } + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = temp; + unLo++; + unHi--; + } + + if (gtHi < ltLo) { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo); + vswap(lo, unLo - n, n); + m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi); + vswap(unLo, hi - m + 1, m); + + n = lo + unLo - ltLo - 1; + m = hi - (gtHi - unHi) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void mainSort() { + int i, j, ss, sb; + int[] runningOrder = new int[256]; + int[] copy = new int[256]; + boolean[] bigDone = new boolean[256]; + int c1, c2; + int numQSorted; + + /* + In the various block-sized structures, live data runs + from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + set up the overshoot area for block. + */ + + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for (i = 0; i < NUM_OVERSHOOT_BYTES; i++) { + block[last + i + 2] = block[(i % (last + 1)) + 1]; + } + for (i = 0; i <= last + NUM_OVERSHOOT_BYTES; i++) { + quadrant[i] = 0; + } + + block[0] = (char) (block[last + 1]); + + if (last < 4000) { + /* + Use simpleSort(), since the full sorting mechanism + has quite a large constant overhead. + */ + for (i = 0; i <= last; i++) { + zptr[i] = i; + } + firstAttempt = false; + workDone = workLimit = 0; + simpleSort(0, last, 0); + } else { + numQSorted = 0; + for (i = 0; i <= 255; i++) { + bigDone[i] = false; + } + + for (i = 0; i <= 65536; i++) { + ftab[i] = 0; + } + + c1 = block[0]; + for (i = 0; i <= last; i++) { + c2 = block[i + 1]; + ftab[(c1 << 8) + c2]++; + c1 = c2; + } + + for (i = 1; i <= 65536; i++) { + ftab[i] += ftab[i - 1]; + } + + c1 = block[1]; + for (i = 0; i < last; i++) { + c2 = block[i + 2]; + j = (c1 << 8) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ((block[last + 1]) << 8) + (block[1]); + ftab[j]--; + zptr[ftab[j]] = last; + + /* + Now ftab contains the first loc of every small bucket. + Calculate the running order, from smallest to largest + big bucket. + */ + + for (i = 0; i <= 255; i++) { + runningOrder[i] = i; + } + + { + int vv; + int h = 1; + do { + h = 3 * h + 1; + } + while (h <= 256); + do { + h = h / 3; + for (i = h; i <= 255; i++) { + vv = runningOrder[i]; + j = i; + while ((ftab[((runningOrder[j - h]) + 1) << 8] + - ftab[(runningOrder[j - h]) << 8]) > + (ftab[((vv) + 1) << 8] - ftab[(vv) << 8])) { + runningOrder[j] = runningOrder[j - h]; + j = j - h; + if (j <= (h - 1)) { + break; + } + } + runningOrder[j] = vv; + } + } while (h != 1); + } + + /* + The main sorting loop. + */ + for (i = 0; i <= 255; i++) { + + /* + Process big buckets, starting with the least full. + */ + ss = runningOrder[i]; + + /* + Complete the big bucket [ss] by quicksorting + any unsorted small buckets [ss, j]. Hopefully + previous pointer-scanning phases have already + completed many of the small buckets [ss, j], so + we don't have to sort them at all. + */ + for (j = 0; j <= 255; j++) { + sb = (ss << 8) + j; + if (!((ftab[sb] & SETMASK) == SETMASK)) { + int lo = ftab[sb] & CLEARMASK; + int hi = (ftab[sb + 1] & CLEARMASK) - 1; + if (hi > lo) { + qSort3(lo, hi, 2); + numQSorted += (hi - lo + 1); + if (workDone > workLimit && firstAttempt) { + return; + } + } + ftab[sb] |= SETMASK; + } + } + + /* + The ss big bucket is now done. Record this fact, + and update the quadrant descriptors. Remember to + update quadrants in the overshoot area too, if + necessary. The "if (i < 255)" test merely skips + this updating for the last bucket processed, since + updating for the last bucket is pointless. + */ + bigDone[ss] = true; + + if (i < 255) { + int bbStart = ftab[ss << 8] & CLEARMASK; + int bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart; + int shifts = 0; + + while ((bbSize >> shifts) > 65534) { + shifts++; + } + + for (j = 0; j < bbSize; j++) { + int a2update = zptr[bbStart + j]; + int qVal = (j >> shifts); + quadrant[a2update] = qVal; + if (a2update < NUM_OVERSHOOT_BYTES) { + quadrant[a2update + last + 1] = qVal; + } + } + + if (!(((bbSize - 1) >> shifts) <= 65535)) { + panic(); + } + } + + /* + Now scan this big bucket so as to synthesise the + sorted order for small buckets [t, ss] for all t != ss. + */ + for (j = 0; j <= 255; j++) { + copy[j] = ftab[(j << 8) + ss] & CLEARMASK; + } + + for (j = ftab[ss << 8] & CLEARMASK; + j < (ftab[(ss + 1) << 8] & CLEARMASK); j++) { + c1 = block[zptr[j]]; + if (!bigDone[c1]) { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for (j = 0; j <= 255; j++) { + ftab[(j << 8) + ss] |= SETMASK; + } + } + } + } + + private void randomiseBlock() { + int i; + int rNToGo = 0; + int rTPos = 0; + for (i = 0; i < 256; i++) { + inUse[i] = false; + } + + for (i = 0; i <= last; i++) { + if (rNToGo == 0) { + rNToGo = (char) rNums[rTPos]; + rTPos++; + if (rTPos == 512) { + rTPos = 0; + } + } + rNToGo--; + block[i + 1] ^= ((rNToGo == 1) ? 1 : 0); + // handle 16 bit signed numbers + block[i + 1] &= 0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void doReversibleTransformation() { + int i; + + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + mainSort(); + + if (workDone > workLimit && firstAttempt) { + randomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + mainSort(); + } + + origPtr = -1; + for (i = 0; i <= last; i++) { + if (zptr[i] == 0) { + origPtr = i; + break; + } + } + + if (origPtr == -1) { + panic(); + } + } + + private boolean fullGtU(int i1, int i2) { + int k; + char c1, c2; + int s1, s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + i1++; + i2++; + + k = last + 1; + + do { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) { + return (s1 > s2); + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) { + return (c1 > c2); + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) { + return (s1 > s2); + } + i1++; + i2++; + + if (i1 > last) { + i1 -= last; + i1--; + } + if (i2 > last) { + i2 -= last; + i2--; + } + + k -= 4; + workDone++; + } while (k >= 0); + + return false; + } + + /* + Knuth's increments seem to work better + than Incerpi-Sedgewick here. Possibly + because the number of elems to sort is + usually small, typically <= 20. + */ + private int[] incs = { 1, 4, 13, 40, 121, 364, 1093, 3280, + 9841, 29524, 88573, 265720, + 797161, 2391484 }; + + private void allocateCompressStructures () { + int n = baseBlockSize * blockSize100k; + block = new char[(n + 1 + NUM_OVERSHOOT_BYTES)]; + quadrant = new int[(n + NUM_OVERSHOOT_BYTES)]; + zptr = new int[n]; + ftab = new int[65537]; + + if (block == null || quadrant == null || zptr == null + || ftab == null) { + //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + //compressOutOfMemory ( totalDraw, n ); + } + + /* + The back end needs a place to store the MTF values + whilst it calculates the coding tables. We could + put them in the zptr array. However, these values + will fit in a short, so we overlay szptr at the + start of zptr, in the hope of reducing the number + of cache misses induced by the multiple traversals + of the MTF values when calculating coding tables. + Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + + szptr = new short[2 * n]; + } + + private void generateMTFValues() { + char[] yy = new char[256]; + int i, j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + makeMaps(); + EOB = nInUse + 1; + + for (i = 0; i <= EOB; i++) { + mtfFreq[i] = 0; + } + + wr = 0; + zPend = 0; + for (i = 0; i < nInUse; i++) { + yy[i] = (char) i; + } + + + for (i = 0; i <= last; i++) { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while (ll_i != tmp) { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + } + yy[0] = tmp; + + if (j == 0) { + zPend++; + } else { + if (zPend > 0) { + zPend--; + while (true) { + switch (zPend % 2) { + case 0: + szptr[wr] = (short) RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = (short) RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + if (zPend < 2) { + break; + } + zPend = (zPend - 2) / 2; + } + zPend = 0; + } + szptr[wr] = (short) (j + 1); + wr++; + mtfFreq[j + 1]++; + } + } + + if (zPend > 0) { + zPend--; + while (true) { + switch (zPend % 2) { + case 0: + szptr[wr] = (short) RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = (short) RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + if (zPend < 2) { + break; + } + zPend = (zPend - 2) / 2; + } + } + + szptr[wr] = (short) EOB; + wr++; + mtfFreq[EOB]++; + + nMTF = wr; + } +} + + diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CRC.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CRC.java new file mode 100644 index 000000000..acb2e80ee --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/apache/bzip2/CRC.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + * + */ + +/* + * This package is based on the work done by Keiron Liddle, Aftex Software + * <keiron@aftexsw.com> to whom the Ant project is very grateful for his + * great code. + */ + +package org.spongycastle.apache.bzip2; + +/** + * A simple class the hold and calculate the CRC for sanity checking + * of the data. + * + * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> + */ +class CRC { + public static int crc32Table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + public CRC() { + initialiseCRC(); + } + + void initialiseCRC() { + globalCrc = 0xffffffff; + } + + int getFinalCRC() { + return ~globalCrc; + } + + int getGlobalCRC() { + return globalCrc; + } + + void setGlobalCRC(int newCrc) { + globalCrc = newCrc; + } + + void updateCRC(int inCh) { + int temp = (globalCrc >> 24) ^ inCh; + if (temp < 0) { + temp = 256 + temp; + } + globalCrc = (globalCrc << 8) ^ CRC.crc32Table[temp]; + } + + int globalCrc; +} + diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ArmoredInputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ArmoredInputStream.java new file mode 100644 index 000000000..2a0f9e3ee --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ArmoredInputStream.java @@ -0,0 +1,473 @@ +package org.spongycastle.bcpg; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; + +/** + * reader for Base64 armored objects - read the headers and then start returning + * bytes when the data is reached. An IOException is thrown if the CRC check + * fails. + */ +public class ArmoredInputStream + extends InputStream +{ + /* + * set up the decoding table. + */ + private static final byte[] decodingTable; + + static + { + decodingTable = new byte[128]; + + for (int i = 'A'; i <= 'Z'; i++) + { + decodingTable[i] = (byte)(i - 'A'); + } + + for (int i = 'a'; i <= 'z'; i++) + { + decodingTable[i] = (byte)(i - 'a' + 26); + } + + for (int i = '0'; i <= '9'; i++) + { + decodingTable[i] = (byte)(i - '0' + 52); + } + + decodingTable['+'] = 62; + decodingTable['/'] = 63; + } + + /** + * decode the base 64 encoded input data. + * + * @return the offset the data starts in out. + */ + private int decode( + int in0, + int in1, + int in2, + int in3, + int[] out) + throws EOFException + { + int b1, b2, b3, b4; + + if (in3 < 0) + { + throw new EOFException("unexpected end of file in armored stream."); + } + + if (in2 == '=') + { + b1 = decodingTable[in0] &0xff; + b2 = decodingTable[in1] & 0xff; + + out[2] = ((b1 << 2) | (b2 >> 4)) & 0xff; + + return 2; + } + else if (in3 == '=') + { + b1 = decodingTable[in0]; + b2 = decodingTable[in1]; + b3 = decodingTable[in2]; + + out[1] = ((b1 << 2) | (b2 >> 4)) & 0xff; + out[2] = ((b2 << 4) | (b3 >> 2)) & 0xff; + + return 1; + } + else + { + b1 = decodingTable[in0]; + b2 = decodingTable[in1]; + b3 = decodingTable[in2]; + b4 = decodingTable[in3]; + + out[0] = ((b1 << 2) | (b2 >> 4)) & 0xff; + out[1] = ((b2 << 4) | (b3 >> 2)) & 0xff; + out[2] = ((b3 << 6) | b4) & 0xff; + + return 0; + } + } + + InputStream in; + boolean start = true; + int[] outBuf = new int[3]; + int bufPtr = 3; + CRC24 crc = new CRC24(); + boolean crcFound = false; + boolean hasHeaders = true; + String header = null; + boolean newLineFound = false; + boolean clearText = false; + boolean restart = false; + Vector headerList= new Vector(); + int lastC = 0; + boolean isEndOfStream; + + /** + * Create a stream for reading a PGP armoured message, parsing up to a header + * and then reading the data that follows. + * + * @param in + */ + public ArmoredInputStream( + InputStream in) + throws IOException + { + this(in, true); + } + + /** + * Create an armoured input stream which will assume the data starts + * straight away, or parse for headers first depending on the value of + * hasHeaders. + * + * @param in + * @param hasHeaders true if headers are to be looked for, false otherwise. + */ + public ArmoredInputStream( + InputStream in, + boolean hasHeaders) + throws IOException + { + this.in = in; + this.hasHeaders = hasHeaders; + + if (hasHeaders) + { + parseHeaders(); + } + + start = false; + } + + public int available() + throws IOException + { + return in.available(); + } + + private boolean parseHeaders() + throws IOException + { + header = null; + + int c; + int last = 0; + boolean headerFound = false; + + headerList = new Vector(); + + // + // if restart we already have a header + // + if (restart) + { + headerFound = true; + } + else + { + while ((c = in.read()) >= 0) + { + if (c == '-' && (last == 0 || last == '\n' || last == '\r')) + { + headerFound = true; + break; + } + + last = c; + } + } + + if (headerFound) + { + StringBuffer buf = new StringBuffer("-"); + boolean eolReached = false; + boolean crLf = false; + + if (restart) // we've had to look ahead two '-' + { + buf.append('-'); + } + + while ((c = in.read()) >= 0) + { + if (last == '\r' && c == '\n') + { + crLf = true; + } + if (eolReached && (last != '\r' && c == '\n')) + { + break; + } + if (eolReached && c == '\r') + { + break; + } + if (c == '\r' || (last != '\r' && c == '\n')) + { + String line = buf.toString(); + if (line.trim().length() == 0) + { + break; + } + headerList.addElement(line); + buf.setLength(0); + } + + if (c != '\n' && c != '\r') + { + buf.append((char)c); + eolReached = false; + } + else + { + if (c == '\r' || (last != '\r' && c == '\n')) + { + eolReached = true; + } + } + + last = c; + } + + if (crLf) + { + in.read(); // skip last \n + } + } + + if (headerList.size() > 0) + { + header = (String)headerList.elementAt(0); + } + + clearText = "-----BEGIN PGP SIGNED MESSAGE-----".equals(header); + newLineFound = true; + + return headerFound; + } + + /** + * @return true if we are inside the clear text section of a PGP + * signed message. + */ + public boolean isClearText() + { + return clearText; + } + + /** + * @return true if the stream is actually at end of file. + */ + public boolean isEndOfStream() + { + return isEndOfStream; + } + + /** + * Return the armor header line (if there is one) + * @return the armor header line, null if none present. + */ + public String getArmorHeaderLine() + { + return header; + } + + /** + * Return the armor headers (the lines after the armor header line), + * @return an array of armor headers, null if there aren't any. + */ + public String[] getArmorHeaders() + { + if (headerList.size() <= 1) + { + return null; + } + + String[] hdrs = new String[headerList.size() - 1]; + + for (int i = 0; i != hdrs.length; i++) + { + hdrs[i] = (String)headerList.elementAt(i + 1); + } + + return hdrs; + } + + private int readIgnoreSpace() + throws IOException + { + int c = in.read(); + + while (c == ' ' || c == '\t') + { + c = in.read(); + } + + return c; + } + + public int read() + throws IOException + { + int c; + + if (start) + { + if (hasHeaders) + { + parseHeaders(); + } + + crc.reset(); + start = false; + } + + if (clearText) + { + c = in.read(); + + if (c == '\r' || (c == '\n' && lastC != '\r')) + { + newLineFound = true; + } + else if (newLineFound && c == '-') + { + c = in.read(); + if (c == '-') // a header, not dash escaped + { + clearText = false; + start = true; + restart = true; + } + else // a space - must be a dash escape + { + c = in.read(); + } + newLineFound = false; + } + else + { + if (c != '\n' && lastC != '\r') + { + newLineFound = false; + } + } + + lastC = c; + + if (c < 0) + { + isEndOfStream = true; + } + + return c; + } + + if (bufPtr > 2 || crcFound) + { + c = readIgnoreSpace(); + + if (c == '\r' || c == '\n') + { + c = readIgnoreSpace(); + + while (c == '\n' || c == '\r') + { + c = readIgnoreSpace(); + } + + if (c < 0) // EOF + { + isEndOfStream = true; + return -1; + } + + if (c == '=') // crc reached + { + bufPtr = decode(readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), outBuf); + if (bufPtr == 0) + { + int i = ((outBuf[0] & 0xff) << 16) + | ((outBuf[1] & 0xff) << 8) + | (outBuf[2] & 0xff); + + crcFound = true; + + if (i != crc.getValue()) + { + throw new IOException("crc check failed in armored message."); + } + return read(); + } + else + { + throw new IOException("no crc found in armored message."); + } + } + else if (c == '-') // end of record reached + { + while ((c = in.read()) >= 0) + { + if (c == '\n' || c == '\r') + { + break; + } + } + + if (!crcFound) + { + throw new IOException("crc check not found."); + } + + crcFound = false; + start = true; + bufPtr = 3; + + if (c < 0) + { + isEndOfStream = true; + } + + return -1; + } + else // data + { + bufPtr = decode(c, readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), outBuf); + } + } + else + { + if (c >= 0) + { + bufPtr = decode(c, readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), outBuf); + } + else + { + isEndOfStream = true; + return -1; + } + } + } + + c = outBuf[bufPtr++]; + + crc.update(c); + + return c; + } + + public void close() + throws IOException + { + in.close(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ArmoredOutputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ArmoredOutputStream.java new file mode 100644 index 000000000..02f46efec --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ArmoredOutputStream.java @@ -0,0 +1,411 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * Basic output stream. + */ +public class ArmoredOutputStream + extends OutputStream +{ + private static final byte[] encodingTable = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', + (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', + (byte)'7', (byte)'8', (byte)'9', + (byte)'+', (byte)'/' + }; + + /** + * encode the input data producing a base 64 encoded byte array. + */ + private void encode( + OutputStream out, + int[] data, + int len) + throws IOException + { + int d1, d2, d3; + + switch (len) + { + case 0: /* nothing left to do */ + break; + case 1: + d1 = data[0]; + + out.write(encodingTable[(d1 >>> 2) & 0x3f]); + out.write(encodingTable[(d1 << 4) & 0x3f]); + out.write('='); + out.write('='); + break; + case 2: + d1 = data[0]; + d2 = data[1]; + + out.write(encodingTable[(d1 >>> 2) & 0x3f]); + out.write(encodingTable[((d1 << 4) | (d2 >>> 4)) & 0x3f]); + out.write(encodingTable[(d2 << 2) & 0x3f]); + out.write('='); + break; + case 3: + d1 = data[0]; + d2 = data[1]; + d3 = data[2]; + + out.write(encodingTable[(d1 >>> 2) & 0x3f]); + out.write(encodingTable[((d1 << 4) | (d2 >>> 4)) & 0x3f]); + out.write(encodingTable[((d2 << 2) | (d3 >>> 6)) & 0x3f]); + out.write(encodingTable[d3 & 0x3f]); + break; + default: + throw new IOException("unknown length in encode"); + } + } + + OutputStream out; + int[] buf = new int[3]; + int bufPtr = 0; + CRC24 crc = new CRC24(); + int chunkCount = 0; + int lastb; + + boolean start = true; + boolean clearText = false; + boolean newLine = false; + + String nl = System.getProperty("line.separator"); + + String type; + String headerStart = "-----BEGIN PGP "; + String headerTail = "-----"; + String footerStart = "-----END PGP "; + String footerTail = "-----"; + + String version = "BCPG v@RELEASE_NAME@"; + + Hashtable headers = new Hashtable(); + + public ArmoredOutputStream( + OutputStream out) + { + this.out = out; + + if (nl == null) + { + nl = "\r\n"; + } + + resetHeaders(); + } + + public ArmoredOutputStream( + OutputStream out, + Hashtable headers) + { + this(out); + + Enumeration e = headers.keys(); + + while (e.hasMoreElements()) + { + Object key = e.nextElement(); + + this.headers.put(key, headers.get(key)); + } + } + + /** + * Set an additional header entry. + * + * @param name the name of the header entry. + * @param value the value of the header entry. + */ + public void setHeader( + String name, + String value) + { + this.headers.put(name, value); + } + + /** + * Reset the headers to only contain a Version string. + */ + public void resetHeaders() + { + headers.clear(); + headers.put("Version", version); + } + + /** + * Start a clear text signed message. + * @param hashAlgorithm + */ + public void beginClearText( + int hashAlgorithm) + throws IOException + { + String hash; + + switch (hashAlgorithm) + { + case HashAlgorithmTags.SHA1: + hash = "SHA1"; + break; + case HashAlgorithmTags.SHA256: + hash = "SHA256"; + break; + case HashAlgorithmTags.SHA384: + hash = "SHA384"; + break; + case HashAlgorithmTags.SHA512: + hash = "SHA512"; + break; + case HashAlgorithmTags.MD2: + hash = "MD2"; + break; + case HashAlgorithmTags.MD5: + hash = "MD5"; + break; + case HashAlgorithmTags.RIPEMD160: + hash = "RIPEMD160"; + break; + default: + throw new IOException("unknown hash algorithm tag in beginClearText: " + hashAlgorithm); + } + + String armorHdr = "-----BEGIN PGP SIGNED MESSAGE-----" + nl; + String hdrs = "Hash: " + hash + nl + nl; + + for (int i = 0; i != armorHdr.length(); i++) + { + out.write(armorHdr.charAt(i)); + } + + for (int i = 0; i != hdrs.length(); i++) + { + out.write(hdrs.charAt(i)); + } + + clearText = true; + newLine = true; + lastb = 0; + } + + public void endClearText() + { + clearText = false; + } + + private void writeHeaderEntry( + String name, + String value) + throws IOException + { + for (int i = 0; i != name.length(); i++) + { + out.write(name.charAt(i)); + } + + out.write(':'); + out.write(' '); + + for (int i = 0; i != value.length(); i++) + { + out.write(value.charAt(i)); + } + + for (int i = 0; i != nl.length(); i++) + { + out.write(nl.charAt(i)); + } + } + + public void write( + int b) + throws IOException + { + if (clearText) + { + out.write(b); + + if (newLine) + { + if (!(b == '\n' && lastb == '\r')) + { + newLine = false; + } + if (b == '-') + { + out.write(' '); + out.write('-'); // dash escape + } + } + if (b == '\r' || (b == '\n' && lastb != '\r')) + { + newLine = true; + } + lastb = b; + return; + } + + if (start) + { + boolean newPacket = (b & 0x40) != 0; + int tag = 0; + + if (newPacket) + { + tag = b & 0x3f; + } + else + { + tag = (b & 0x3f) >> 2; + } + + switch (tag) + { + case PacketTags.PUBLIC_KEY: + type = "PUBLIC KEY BLOCK"; + break; + case PacketTags.SECRET_KEY: + type = "PRIVATE KEY BLOCK"; + break; + case PacketTags.SIGNATURE: + type = "SIGNATURE"; + break; + default: + type = "MESSAGE"; + } + + for (int i = 0; i != headerStart.length(); i++) + { + out.write(headerStart.charAt(i)); + } + + for (int i = 0; i != type.length(); i++) + { + out.write(type.charAt(i)); + } + + for (int i = 0; i != headerTail.length(); i++) + { + out.write(headerTail.charAt(i)); + } + + for (int i = 0; i != nl.length(); i++) + { + out.write(nl.charAt(i)); + } + + writeHeaderEntry("Version", (String)headers.get("Version")); + + Enumeration e = headers.keys(); + while (e.hasMoreElements()) + { + String key = (String)e.nextElement(); + + if (!key.equals("Version")) + { + writeHeaderEntry(key, (String)headers.get(key)); + } + } + + for (int i = 0; i != nl.length(); i++) + { + out.write(nl.charAt(i)); + } + + start = false; + } + + if (bufPtr == 3) + { + encode(out, buf, bufPtr); + bufPtr = 0; + if ((++chunkCount & 0xf) == 0) + { + for (int i = 0; i != nl.length(); i++) + { + out.write(nl.charAt(i)); + } + } + } + + crc.update(b); + buf[bufPtr++] = b & 0xff; + } + + public void flush() + throws IOException + { + } + + /** + * <b>Note</b>: close does nor close the underlying stream. So it is possible to write + * multiple objects using armoring to a single stream. + */ + public void close() + throws IOException + { + if (type != null) + { + encode(out, buf, bufPtr); + + for (int i = 0; i != nl.length(); i++) + { + out.write(nl.charAt(i)); + } + out.write('='); + + int crcV = crc.getValue(); + + buf[0] = ((crcV >> 16) & 0xff); + buf[1] = ((crcV >> 8) & 0xff); + buf[2] = (crcV & 0xff); + + encode(out, buf, 3); + + for (int i = 0; i != nl.length(); i++) + { + out.write(nl.charAt(i)); + } + + for (int i = 0; i != footerStart.length(); i++) + { + out.write(footerStart.charAt(i)); + } + + for (int i = 0; i != type.length(); i++) + { + out.write(type.charAt(i)); + } + + for (int i = 0; i != footerTail.length(); i++) + { + out.write(footerTail.charAt(i)); + } + + for (int i = 0; i != nl.length(); i++) + { + out.write(nl.charAt(i)); + } + + out.flush(); + + type = null; + start = true; + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGInputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGInputStream.java new file mode 100644 index 000000000..77b0a364a --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGInputStream.java @@ -0,0 +1,391 @@ +package org.spongycastle.bcpg; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.util.io.Streams; + +/** + * reader for PGP objects + */ +public class BCPGInputStream + extends InputStream implements PacketTags +{ + InputStream in; + boolean next = false; + int nextB; + + public BCPGInputStream( + InputStream in) + { + this.in = in; + } + + public int available() + throws IOException + { + return in.available(); + } + + public int read() + throws IOException + { + if (next) + { + next = false; + + return nextB; + } + else + { + return in.read(); + } + } + + public int read( + byte[] buf, + int off, + int len) + throws IOException + { + if (len == 0) + { + return 0; + } + + if (!next) + { + return in.read(buf, off, len); + } + + // We have next byte waiting, so return it + + if (nextB < 0) + { + return -1; // EOF + } + + buf[off] = (byte)nextB; // May throw NullPointerException... + next = false; // ...so only set this afterwards + + return 1; + } + + public void readFully( + byte[] buf, + int off, + int len) + throws IOException + { + if (Streams.readFully(this, buf, off, len) < len) + { + throw new EOFException(); + } + } + + public byte[] readAll() + throws IOException + { + return Streams.readAll(this); + } + + public void readFully( + byte[] buf) + throws IOException + { + readFully(buf, 0, buf.length); + } + + /** + * returns the next packet tag in the stream. + * + * @return the tag number. + * + * @throws IOException + */ + public int nextPacketTag() + throws IOException + { + if (!next) + { + try + { + nextB = in.read(); + } + catch (EOFException e) + { + nextB = -1; + } + } + + next = true; + + if (nextB >= 0) + { + if ((nextB & 0x40) != 0) // new + { + return (nextB & 0x3f); + } + else // old + { + return ((nextB & 0x3f) >> 2); + } + } + + return nextB; + } + + public Packet readPacket() + throws IOException + { + int hdr = this.read(); + + if (hdr < 0) + { + return null; + } + + if ((hdr & 0x80) == 0) + { + throw new IOException("invalid header encountered"); + } + + boolean newPacket = (hdr & 0x40) != 0; + int tag = 0; + int bodyLen = 0; + boolean partial = false; + + if (newPacket) + { + tag = hdr & 0x3f; + + int l = this.read(); + + if (l < 192) + { + bodyLen = l; + } + else if (l <= 223) + { + int b = in.read(); + + bodyLen = ((l - 192) << 8) + (b) + 192; + } + else if (l == 255) + { + bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read(); + } + else + { + partial = true; + bodyLen = 1 << (l & 0x1f); + } + } + else + { + int lengthType = hdr & 0x3; + + tag = (hdr & 0x3f) >> 2; + + switch (lengthType) + { + case 0: + bodyLen = this.read(); + break; + case 1: + bodyLen = (this.read() << 8) | this.read(); + break; + case 2: + bodyLen = (this.read() << 24) | (this.read() << 16) | (this.read() << 8) | this.read(); + break; + case 3: + partial = true; + break; + default: + throw new IOException("unknown length type encountered"); + } + } + + BCPGInputStream objStream; + + if (bodyLen == 0 && partial) + { + objStream = this; + } + else + { + objStream = new BCPGInputStream(new PartialInputStream(this, partial, bodyLen)); + } + + switch (tag) + { + case RESERVED: + return new InputStreamPacket(objStream); + case PUBLIC_KEY_ENC_SESSION: + return new PublicKeyEncSessionPacket(objStream); + case SIGNATURE: + return new SignaturePacket(objStream); + case SYMMETRIC_KEY_ENC_SESSION: + return new SymmetricKeyEncSessionPacket(objStream); + case ONE_PASS_SIGNATURE: + return new OnePassSignaturePacket(objStream); + case SECRET_KEY: + return new SecretKeyPacket(objStream); + case PUBLIC_KEY: + return new PublicKeyPacket(objStream); + case SECRET_SUBKEY: + return new SecretSubkeyPacket(objStream); + case COMPRESSED_DATA: + return new CompressedDataPacket(objStream); + case SYMMETRIC_KEY_ENC: + return new SymmetricEncDataPacket(objStream); + case MARKER: + return new MarkerPacket(objStream); + case LITERAL_DATA: + return new LiteralDataPacket(objStream); + case TRUST: + return new TrustPacket(objStream); + case USER_ID: + return new UserIDPacket(objStream); + case USER_ATTRIBUTE: + return new UserAttributePacket(objStream); + case PUBLIC_SUBKEY: + return new PublicSubkeyPacket(objStream); + case SYM_ENC_INTEGRITY_PRO: + return new SymmetricEncIntegrityPacket(objStream); + case MOD_DETECTION_CODE: + return new ModDetectionCodePacket(objStream); + case EXPERIMENTAL_1: + case EXPERIMENTAL_2: + case EXPERIMENTAL_3: + case EXPERIMENTAL_4: + return new ExperimentalPacket(tag, objStream); + default: + throw new IOException("unknown packet type encountered: " + tag); + } + } + + public void close() + throws IOException + { + in.close(); + } + + /** + * a stream that overlays our input stream, allowing the user to only read a segment of it. + * + * NB: dataLength will be negative if the segment length is in the upper range above 2**31. + */ + private static class PartialInputStream + extends InputStream + { + private BCPGInputStream in; + private boolean partial; + private int dataLength; + + PartialInputStream( + BCPGInputStream in, + boolean partial, + int dataLength) + { + this.in = in; + this.partial = partial; + this.dataLength = dataLength; + } + + public int available() + throws IOException + { + int avail = in.available(); + + if (avail <= dataLength || dataLength < 0) + { + return avail; + } + else + { + if (partial && dataLength == 0) + { + return 1; + } + return dataLength; + } + } + + private int loadDataLength() + throws IOException + { + int l = in.read(); + + if (l < 0) + { + return -1; + } + + partial = false; + if (l < 192) + { + dataLength = l; + } + else if (l <= 223) + { + dataLength = ((l - 192) << 8) + (in.read()) + 192; + } + else if (l == 255) + { + dataLength = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read(); + } + else + { + partial = true; + dataLength = 1 << (l & 0x1f); + } + + return dataLength; + } + + public int read(byte[] buf, int offset, int len) + throws IOException + { + do + { + if (dataLength != 0) + { + int readLen = (dataLength > len || dataLength < 0) ? len : dataLength; + readLen = in.read(buf, offset, readLen); + if (readLen < 0) + { + throw new EOFException("premature end of stream in PartialInputStream"); + } + dataLength -= readLen; + return readLen; + } + } + while (partial && loadDataLength() >= 0); + + return -1; + } + + public int read() + throws IOException + { + do + { + if (dataLength != 0) + { + int ch = in.read(); + if (ch < 0) + { + throw new EOFException("premature end of stream in PartialInputStream"); + } + dataLength--; + return ch; + } + } + while (partial && loadDataLength() >= 0); + + return -1; + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGKey.java new file mode 100644 index 000000000..7025bb311 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGKey.java @@ -0,0 +1,24 @@ +package org.spongycastle.bcpg; + +/** + * base interface for a PGP key + */ +public interface BCPGKey +{ + /** + * Return the base format for this key - in the case of the symmetric keys it will generally + * be raw indicating that the key is just a straight byte representation, for an asymmetric + * key the format will be PGP, indicating the key is a string of MPIs encoded in PGP format. + * + * @return "RAW" or "PGP" + */ + public String getFormat(); + + /** + * return a string of bytes giving the encoded format of the key, as described by it's format. + * + * @return byte[] + */ + public byte[] getEncoded(); + +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGObject.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGObject.java new file mode 100644 index 000000000..55d14d896 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGObject.java @@ -0,0 +1,24 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * base class for a PGP object. + */ +public abstract class BCPGObject +{ + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.writeObject(this); + + return bOut.toByteArray(); + } + + public abstract void encode(BCPGOutputStream out) + throws IOException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGOutputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGOutputStream.java new file mode 100644 index 000000000..560a64564 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/BCPGOutputStream.java @@ -0,0 +1,361 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Basic output stream. + */ +public class BCPGOutputStream + extends OutputStream + implements PacketTags, CompressionAlgorithmTags +{ + OutputStream out; + private byte[] partialBuffer; + private int partialBufferLength; + private int partialPower; + private int partialOffset; + + private static final int BUF_SIZE_POWER = 16; // 2^16 size buffer on long files + + public BCPGOutputStream( + OutputStream out) + { + this.out = out; + } + + /** + * Create a stream representing an old style partial object. + * + * @param tag the packet tag for the object. + */ + public BCPGOutputStream( + OutputStream out, + int tag) + throws IOException + { + this.out = out; + this.writeHeader(tag, true, true, 0); + } + + /** + * Create a stream representing a general packet. + * + * @param out + * @param tag + * @param length + * @param oldFormat + * @throws IOException + */ + public BCPGOutputStream( + OutputStream out, + int tag, + long length, + boolean oldFormat) + throws IOException + { + this.out = out; + + if (length > 0xFFFFFFFFL) + { + this.writeHeader(tag, false, true, 0); + this.partialBufferLength = 1 << BUF_SIZE_POWER; + this.partialBuffer = new byte[partialBufferLength]; + this.partialPower = BUF_SIZE_POWER; + this.partialOffset = 0; + } + else + { + this.writeHeader(tag, oldFormat, false, length); + } + } + + /** + * + * @param tag + * @param length + * @throws IOException + */ + public BCPGOutputStream( + OutputStream out, + int tag, + long length) + throws IOException + { + this.out = out; + + this.writeHeader(tag, false, false, length); + } + + /** + * Create a new style partial input stream buffered into chunks. + * + * @param out output stream to write to. + * @param tag packet tag. + * @param buffer size of chunks making up the packet. + * @throws IOException + */ + public BCPGOutputStream( + OutputStream out, + int tag, + byte[] buffer) + throws IOException + { + this.out = out; + this.writeHeader(tag, false, true, 0); + + this.partialBuffer = buffer; + + int length = partialBuffer.length; + + for (partialPower = 0; length != 1; partialPower++) + { + length >>>= 1; + } + + if (partialPower > 30) + { + throw new IOException("Buffer cannot be greater than 2^30 in length."); + } + + this.partialBufferLength = 1 << partialPower; + this.partialOffset = 0; + } + + private void writeNewPacketLength( + long bodyLen) + throws IOException + { + if (bodyLen < 192) + { + out.write((byte)bodyLen); + } + else if (bodyLen <= 8383) + { + bodyLen -= 192; + + out.write((byte)(((bodyLen >> 8) & 0xff) + 192)); + out.write((byte)bodyLen); + } + else + { + out.write(0xff); + out.write((byte)(bodyLen >> 24)); + out.write((byte)(bodyLen >> 16)); + out.write((byte)(bodyLen >> 8)); + out.write((byte)bodyLen); + } + } + + private void writeHeader( + int tag, + boolean oldPackets, + boolean partial, + long bodyLen) + throws IOException + { + int hdr = 0x80; + + if (partialBuffer != null) + { + partialFlush(true); + partialBuffer = null; + } + + if (oldPackets) + { + hdr |= tag << 2; + + if (partial) + { + this.write(hdr | 0x03); + } + else + { + if (bodyLen <= 0xff) + { + this.write(hdr); + this.write((byte)bodyLen); + } + else if (bodyLen <= 0xffff) + { + this.write(hdr | 0x01); + this.write((byte)(bodyLen >> 8)); + this.write((byte)(bodyLen)); + } + else + { + this.write(hdr | 0x02); + this.write((byte)(bodyLen >> 24)); + this.write((byte)(bodyLen >> 16)); + this.write((byte)(bodyLen >> 8)); + this.write((byte)bodyLen); + } + } + } + else + { + hdr |= 0x40 | tag; + this.write(hdr); + + if (partial) + { + partialOffset = 0; + } + else + { + this.writeNewPacketLength(bodyLen); + } + } + } + + private void partialFlush( + boolean isLast) + throws IOException + { + if (isLast) + { + writeNewPacketLength(partialOffset); + out.write(partialBuffer, 0, partialOffset); + } + else + { + out.write(0xE0 | partialPower); + out.write(partialBuffer, 0, partialBufferLength); + } + + partialOffset = 0; + } + + private void writePartial( + byte b) + throws IOException + { + if (partialOffset == partialBufferLength) + { + partialFlush(false); + } + + partialBuffer[partialOffset++] = b; + } + + private void writePartial( + byte[] buf, + int off, + int len) + throws IOException + { + if (partialOffset == partialBufferLength) + { + partialFlush(false); + } + + if (len <= (partialBufferLength - partialOffset)) + { + System.arraycopy(buf, off, partialBuffer, partialOffset, len); + partialOffset += len; + } + else + { + System.arraycopy(buf, off, partialBuffer, partialOffset, partialBufferLength - partialOffset); + off += partialBufferLength - partialOffset; + len -= partialBufferLength - partialOffset; + partialFlush(false); + + while (len > partialBufferLength) + { + System.arraycopy(buf, off, partialBuffer, 0, partialBufferLength); + off += partialBufferLength; + len -= partialBufferLength; + partialFlush(false); + } + + System.arraycopy(buf, off, partialBuffer, 0, len); + partialOffset += len; + } + } + + public void write( + int b) + throws IOException + { + if (partialBuffer != null) + { + writePartial((byte)b); + } + else + { + out.write(b); + } + } + + public void write( + byte[] bytes, + int off, + int len) + throws IOException + { + if (partialBuffer != null) + { + writePartial(bytes, off, len); + } + else + { + out.write(bytes, off, len); + } + } + + public void writePacket( + ContainedPacket p) + throws IOException + { + p.encode(this); + } + + void writePacket( + int tag, + byte[] body, + boolean oldFormat) + throws IOException + { + this.writeHeader(tag, oldFormat, false, body.length); + this.write(body); + } + + public void writeObject( + BCPGObject o) + throws IOException + { + o.encode(this); + } + + /** + * Flush the underlying stream. + */ + public void flush() + throws IOException + { + out.flush(); + } + + /** + * Finish writing out the current packet without closing the underlying stream. + */ + public void finish() + throws IOException + { + if (partialBuffer != null) + { + partialFlush(true); + partialBuffer = null; + } + } + + public void close() + throws IOException + { + this.finish(); + out.flush(); + out.close(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CRC24.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CRC24.java new file mode 100644 index 000000000..b9db421d7 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CRC24.java @@ -0,0 +1,37 @@ +package org.spongycastle.bcpg; + +public class CRC24 +{ + private static final int CRC24_INIT = 0x0b704ce; + private static final int CRC24_POLY = 0x1864cfb; + + private int crc = CRC24_INIT; + + public CRC24() + { + } + + public void update( + int b) + { + crc ^= b << 16; + for (int i = 0; i < 8; i++) + { + crc <<= 1; + if ((crc & 0x1000000) != 0) + { + crc ^= CRC24_POLY; + } + } + } + + public int getValue() + { + return crc; + } + + public void reset() + { + crc = CRC24_INIT; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CompressedDataPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CompressedDataPacket.java new file mode 100644 index 000000000..3513f528a --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CompressedDataPacket.java @@ -0,0 +1,31 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +/** + * generic compressed data object. + */ +public class CompressedDataPacket + extends InputStreamPacket +{ + int algorithm; + + CompressedDataPacket( + BCPGInputStream in) + throws IOException + { + super(in); + + algorithm = in.read(); + } + + /** + * return the algorithm tag value. + * + * @return algorithm tag value. + */ + public int getAlgorithm() + { + return algorithm; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CompressionAlgorithmTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CompressionAlgorithmTags.java new file mode 100644 index 000000000..4de0a188e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/CompressionAlgorithmTags.java @@ -0,0 +1,12 @@ +package org.spongycastle.bcpg; + +/** + * Basic tags for compression algorithms + */ +public interface CompressionAlgorithmTags +{ + public static final int UNCOMPRESSED = 0; // Uncompressed + public static final int ZIP = 1; // ZIP (RFC 1951) + public static final int ZLIB = 2; // ZLIB (RFC 1950) + public static final int BZIP2 = 3; // BZ2 +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ContainedPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ContainedPacket.java new file mode 100644 index 000000000..75964559c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ContainedPacket.java @@ -0,0 +1,26 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Basic type for a PGP packet. + */ +public abstract class ContainedPacket + extends Packet +{ + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.writePacket(this); + + return bOut.toByteArray(); + } + + public abstract void encode( + BCPGOutputStream pOut) + throws IOException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/DSAPublicBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/DSAPublicBCPGKey.java new file mode 100644 index 000000000..54d0738c5 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/DSAPublicBCPGKey.java @@ -0,0 +1,116 @@ +package org.spongycastle.bcpg; + +import java.io.*; +import java.math.BigInteger; + +/** + * base class for a DSA Public Key. + */ +public class DSAPublicBCPGKey + extends BCPGObject implements BCPGKey +{ + MPInteger p; + MPInteger q; + MPInteger g; + MPInteger y; + + /** + * @param in the stream to read the packet from. + */ + public DSAPublicBCPGKey( + BCPGInputStream in) + throws IOException + { + this.p = new MPInteger(in); + this.q = new MPInteger(in); + this.g = new MPInteger(in); + this.y = new MPInteger(in); + } + + public DSAPublicBCPGKey( + BigInteger p, + BigInteger q, + BigInteger g, + BigInteger y) + { + this.p = new MPInteger(p); + this.q = new MPInteger(q); + this.g = new MPInteger(g); + this.y = new MPInteger(y); + } + + /** + * return "PGP" + * + * @see org.spongycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see org.spongycastle.bcpg.BCPGKey#getEncoded() + */ + public byte[] getEncoded() + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pgpOut = new BCPGOutputStream(bOut); + + pgpOut.writeObject(this); + + return bOut.toByteArray(); + } + catch (IOException e) + { + return null; + } + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writeObject(p); + out.writeObject(q); + out.writeObject(g); + out.writeObject(y); + } + + /** + * @return g + */ + public BigInteger getG() + { + return g.getValue(); + } + + /** + * @return p + */ + public BigInteger getP() + { + return p.getValue(); + } + + /** + * @return q + */ + public BigInteger getQ() + { + return q.getValue(); + } + + /** + * @return g + */ + public BigInteger getY() + { + return y.getValue(); + } + +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/DSASecretBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/DSASecretBCPGKey.java new file mode 100644 index 000000000..17dd68825 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/DSASecretBCPGKey.java @@ -0,0 +1,82 @@ +package org.spongycastle.bcpg; + +import java.io.*; +import java.math.BigInteger; + +/** + * base class for a DSA Secret Key. + */ +public class DSASecretBCPGKey + extends BCPGObject implements BCPGKey +{ + MPInteger x; + + /** + * + * @param in + * @throws IOException + */ + public DSASecretBCPGKey( + BCPGInputStream in) + throws IOException + { + this.x = new MPInteger(in); + } + + /** + * + * @param x + */ + public DSASecretBCPGKey( + BigInteger x) + { + this.x = new MPInteger(x); + } + + /** + * return "PGP" + * + * @see org.spongycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see org.spongycastle.bcpg.BCPGKey#getEncoded() + */ + public byte[] getEncoded() + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pgpOut = new BCPGOutputStream(bOut); + + pgpOut.writeObject(this); + + return bOut.toByteArray(); + } + catch (IOException e) + { + return null; + } + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writeObject(x); + } + + /** + * @return x + */ + public BigInteger getX() + { + return x.getValue(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECDHPublicBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECDHPublicBCPGKey.java new file mode 100644 index 000000000..a1ddfffb2 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECDHPublicBCPGKey.java @@ -0,0 +1,113 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.math.ec.ECPoint; + +/** + * base class for an ECDH Public Key. + */ +public class ECDHPublicBCPGKey + extends ECPublicBCPGKey +{ + private byte reserved; + private byte hashFunctionId; + private byte symAlgorithmId; + + /** + * @param in the stream to read the packet from. + */ + public ECDHPublicBCPGKey( + BCPGInputStream in) + throws IOException + { + super(in); + + int length = in.read(); + byte[] kdfParameters = new byte[length]; + if (kdfParameters.length != 3) + { + throw new IllegalStateException("kdf parameters size of 3 expected."); + } + + in.read(kdfParameters); + + reserved = kdfParameters[0]; + hashFunctionId = kdfParameters[1]; + symAlgorithmId = kdfParameters[2]; + + verifyHashAlgorithm(); + verifySymmetricKeyAlgorithm(); + } + + public ECDHPublicBCPGKey( + ASN1ObjectIdentifier oid, + ECPoint point, + int hashAlgorithm, + int symmetricKeyAlgorithm) + { + super(oid, point); + + reserved = 1; + hashFunctionId = (byte)hashAlgorithm; + symAlgorithmId = (byte)symmetricKeyAlgorithm; + + verifyHashAlgorithm(); + verifySymmetricKeyAlgorithm(); + } + + public byte getReserved() + { + return reserved; + } + + public byte getHashAlgorithm() + { + return hashFunctionId; + } + + public byte getSymmetricKeyAlgorithm() + { + return symAlgorithmId; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + super.encode(out); + out.write(0x3); + out.write(reserved); + out.write(hashFunctionId); + out.write(symAlgorithmId); + } + + private void verifyHashAlgorithm() + { + switch (hashFunctionId) + { + case HashAlgorithmTags.SHA256: + case HashAlgorithmTags.SHA384: + case HashAlgorithmTags.SHA512: + break; + + default: + throw new IllegalStateException("Hash algorithm must be SHA-256 or stronger."); + } + } + + private void verifySymmetricKeyAlgorithm() + { + switch (symAlgorithmId) + { + case SymmetricKeyAlgorithmTags.AES_128: + case SymmetricKeyAlgorithmTags.AES_192: + case SymmetricKeyAlgorithmTags.AES_256: + break; + + default: + throw new IllegalStateException("Symmetric key algorithm must be AES-128 or stronger."); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECDSAPublicBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECDSAPublicBCPGKey.java new file mode 100644 index 000000000..36db932a6 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECDSAPublicBCPGKey.java @@ -0,0 +1,31 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.math.ec.ECPoint; + +/** + * base class for an ECDSA Public Key. + */ +public class ECDSAPublicBCPGKey + extends ECPublicBCPGKey +{ + /** + * @param in the stream to read the packet from. + */ + protected ECDSAPublicBCPGKey( + BCPGInputStream in) + throws IOException + { + super(in); + } + + public ECDSAPublicBCPGKey( + ASN1ObjectIdentifier oid, + ECPoint point) + { + super(oid, point); + } + +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECPublicBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECPublicBCPGKey.java new file mode 100644 index 000000000..df585146c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECPublicBCPGKey.java @@ -0,0 +1,146 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1Primitive; +import org.spongycastle.asn1.x9.ECNamedCurveTable; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; + +/** + * base class for an EC Public Key. + */ +public abstract class ECPublicBCPGKey + extends BCPGObject + implements BCPGKey +{ + ASN1ObjectIdentifier oid; + ECPoint point; + + /** + * @param in the stream to read the packet from. + */ + protected ECPublicBCPGKey( + BCPGInputStream in) + throws IOException + { + this.oid = ASN1ObjectIdentifier.getInstance(ASN1Primitive.fromByteArray(readBytesOfEncodedLength(in))); + this.point = decodePoint(new MPInteger(in).getValue(), oid); + } + + protected ECPublicBCPGKey( + ASN1ObjectIdentifier oid, + ECPoint point) + { + this.point = point.normalize(); + this.oid = oid; + } + + protected ECPublicBCPGKey( + BigInteger encodedPoint, + ASN1ObjectIdentifier oid) + throws IOException + { + this.point = decodePoint(encodedPoint, oid); + this.oid = oid; + } + + /** + * return "PGP" + * + * @see org.spongycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see org.spongycastle.bcpg.BCPGKey#getEncoded() + */ + public byte[] getEncoded() + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pgpOut = new BCPGOutputStream(bOut); + + pgpOut.writeObject(this); + + return bOut.toByteArray(); + } + catch (IOException e) + { + return null; + } + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + byte[] oid = this.oid.getEncoded(); + out.write(oid, 1, oid.length - 1); + + MPInteger point = new MPInteger(new BigInteger(1, this.point.getEncoded())); + out.writeObject(point); + } + + /** + * @return point + */ + public ECPoint getPoint() + { + return point; + } + + /** + * @return oid + */ + public ASN1ObjectIdentifier getCurveOID() + { + return oid; + } + + protected static byte[] readBytesOfEncodedLength( + BCPGInputStream in) + throws IOException + { + int length = in.read(); + if (length == 0 || length == 0xFF) + { + throw new IOException("future extensions not yet implemented."); + } + + byte[] buffer = new byte[length + 2]; + in.readFully(buffer, 2, buffer.length - 2); + buffer[0] = (byte)0x06; + buffer[1] = (byte)length; + + return buffer; + } + + private static ECPoint decodePoint( + BigInteger encodedPoint, + ASN1ObjectIdentifier oid) + throws IOException + { + X9ECParameters curve = ECNamedCurveTable.getByOID(oid); + if (curve == null) + { + throw new IOException(oid.getId() + " does not match any known curve."); + } + if (!(curve.getCurve() instanceof ECCurve.Fp)) + { + throw new IOException("Only FPCurves are supported."); + } + + return curve.getCurve().decodePoint(encodedPoint.toByteArray()); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECSecretBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECSecretBCPGKey.java new file mode 100644 index 000000000..124a8d120 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ECSecretBCPGKey.java @@ -0,0 +1,82 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; + +/** + * base class for an EC Secret Key. + */ +public class ECSecretBCPGKey + extends BCPGObject + implements BCPGKey +{ + MPInteger x; + + /** + * @param in + * @throws IOException + */ + public ECSecretBCPGKey( + BCPGInputStream in) + throws IOException + { + this.x = new MPInteger(in); + } + + /** + * @param x + */ + public ECSecretBCPGKey( + BigInteger x) + { + this.x = new MPInteger(x); + } + + /** + * return "PGP" + * + * @see org.spongycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see org.spongycastle.bcpg.BCPGKey#getEncoded() + */ + public byte[] getEncoded() + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pgpOut = new BCPGOutputStream(bOut); + + pgpOut.writeObject(this); + + return bOut.toByteArray(); + } + catch (IOException e) + { + return null; + } + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writeObject(x); + } + + /** + * @return x + */ + public BigInteger getX() + { + return x.getValue(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ElGamalPublicBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ElGamalPublicBCPGKey.java new file mode 100644 index 000000000..bb1bc0b4c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ElGamalPublicBCPGKey.java @@ -0,0 +1,93 @@ +package org.spongycastle.bcpg; + +import java.io.*; +import java.math.BigInteger; + +/** + * base class for an ElGamal Public Key. + */ +public class ElGamalPublicBCPGKey + extends BCPGObject implements BCPGKey +{ + MPInteger p; + MPInteger g; + MPInteger y; + + /** + * + */ + public ElGamalPublicBCPGKey( + BCPGInputStream in) + throws IOException + { + this.p = new MPInteger(in); + this.g = new MPInteger(in); + this.y = new MPInteger(in); + } + + public ElGamalPublicBCPGKey( + BigInteger p, + BigInteger g, + BigInteger y) + { + this.p = new MPInteger(p); + this.g = new MPInteger(g); + this.y = new MPInteger(y); + } + + /** + * return "PGP" + * + * @see org.spongycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see org.spongycastle.bcpg.BCPGKey#getEncoded() + */ + public byte[] getEncoded() + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pgpOut = new BCPGOutputStream(bOut); + + pgpOut.writeObject(this); + + return bOut.toByteArray(); + } + catch (IOException e) + { + return null; + } + } + + public BigInteger getP() + { + return p.getValue(); + } + + public BigInteger getG() + { + return g.getValue(); + } + + public BigInteger getY() + { + return y.getValue(); + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writeObject(p); + out.writeObject(g); + out.writeObject(y); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ElGamalSecretBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ElGamalSecretBCPGKey.java new file mode 100644 index 000000000..9a7d2b02b --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ElGamalSecretBCPGKey.java @@ -0,0 +1,79 @@ +package org.spongycastle.bcpg; + +import java.io.*; +import java.math.BigInteger; + +/** + * base class for an ElGamal Secret Key. + */ +public class ElGamalSecretBCPGKey + extends BCPGObject implements BCPGKey +{ + MPInteger x; + + /** + * + * @param in + * @throws IOException + */ + public ElGamalSecretBCPGKey( + BCPGInputStream in) + throws IOException + { + this.x = new MPInteger(in); + } + + /** + * + * @param x + */ + public ElGamalSecretBCPGKey( + BigInteger x) + { + this.x = new MPInteger(x); + } + + /** + * return "PGP" + * + * @see org.spongycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + public BigInteger getX() + { + return x.getValue(); + } + + /** + * return the standard PGP encoding of the key. + * + * @see org.spongycastle.bcpg.BCPGKey#getEncoded() + */ + public byte[] getEncoded() + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pgpOut = new BCPGOutputStream(bOut); + + pgpOut.writeObject(this); + + return bOut.toByteArray(); + } + catch (IOException e) + { + return null; + } + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writeObject(x); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ExperimentalPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ExperimentalPacket.java new file mode 100644 index 000000000..64575c481 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ExperimentalPacket.java @@ -0,0 +1,46 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +import org.spongycastle.util.Arrays; + +/** + * basic packet for an experimental packet. + */ +public class ExperimentalPacket + extends ContainedPacket implements PublicKeyAlgorithmTags +{ + private int tag; + private byte[] contents; + + /** + * + * @param in + * @throws IOException + */ + ExperimentalPacket( + int tag, + BCPGInputStream in) + throws IOException + { + this.tag = tag; + this.contents = in.readAll(); + } + + public int getTag() + { + return tag; + } + + public byte[] getContents() + { + return Arrays.clone(contents); + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(tag, contents, true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/HashAlgorithmTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/HashAlgorithmTags.java new file mode 100644 index 000000000..868451b08 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/HashAlgorithmTags.java @@ -0,0 +1,20 @@ +package org.spongycastle.bcpg; + +/** + * basic tags for hash algorithms + */ +public interface HashAlgorithmTags +{ + public static final int MD5 = 1; // MD5 + public static final int SHA1 = 2; // SHA-1 + public static final int RIPEMD160 = 3; // RIPE-MD/160 + public static final int DOUBLE_SHA = 4; // Reserved for double-width SHA (experimental) + public static final int MD2 = 5; // MD2 + public static final int TIGER_192 = 6; // Reserved for TIGER/192 + public static final int HAVAL_5_160 = 7; // Reserved for HAVAL (5 pass, 160-bit) + + public static final int SHA256 = 8; // SHA-256 + public static final int SHA384 = 9; // SHA-384 + public static final int SHA512 = 10; // SHA-512 + public static final int SHA224 = 11; // SHA-224 +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/InputStreamPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/InputStreamPacket.java new file mode 100644 index 000000000..183a79135 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/InputStreamPacket.java @@ -0,0 +1,26 @@ +package org.spongycastle.bcpg; + +/** + * + */ +public class InputStreamPacket + extends Packet +{ + private BCPGInputStream in; + + public InputStreamPacket( + BCPGInputStream in) + { + this.in = in; + } + + /** + * Note: you can only read from this once... + * + * @return the InputStream + */ + public BCPGInputStream getInputStream() + { + return in; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/LiteralDataPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/LiteralDataPacket.java new file mode 100644 index 000000000..b9c8acfb0 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/LiteralDataPacket.java @@ -0,0 +1,74 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +import org.spongycastle.util.Strings; + +/** + * generic literal data packet. + */ +public class LiteralDataPacket + extends InputStreamPacket +{ + int format; + byte[] fileName; + long modDate; + + LiteralDataPacket( + BCPGInputStream in) + throws IOException + { + super(in); + + format = in.read(); + int l = in.read(); + + fileName = new byte[l]; + for (int i = 0; i != fileName.length; i++) + { + fileName[i] = (byte)in.read(); + } + + modDate = ((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read(); + } + + /** + * return the format tag value. + * + * @return format tag value. + */ + public int getFormat() + { + return format; + } + + /** + * Return the modification time of the file in milli-seconds. + * + * @return the modification time in millis + */ + public long getModificationTime() + { + return modDate * 1000L; + } + + /** + * @return filename + */ + public String getFileName() + { + return Strings.fromUTF8ByteArray(fileName); + } + + public byte[] getRawFileName() + { + byte[] tmp = new byte[fileName.length]; + + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = fileName[i]; + } + + return tmp; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/MPInteger.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/MPInteger.java new file mode 100644 index 000000000..05abda374 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/MPInteger.java @@ -0,0 +1,62 @@ +package org.spongycastle.bcpg; + +import java.io.*; +import java.math.BigInteger; + +/** + * a multiple precision integer + */ +public class MPInteger + extends BCPGObject +{ + BigInteger value = null; + + public MPInteger( + BCPGInputStream in) + throws IOException + { + int length = (in.read() << 8) | in.read(); + byte[] bytes = new byte[(length + 7) / 8]; + + in.readFully(bytes); + + value = new BigInteger(1, bytes); + } + + public MPInteger( + BigInteger value) + { + if (value == null || value.signum() < 0) + { + throw new IllegalArgumentException("value must not be null, or negative"); + } + + this.value = value; + } + + public BigInteger getValue() + { + return value; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + int length = value.bitLength(); + + out.write(length >> 8); + out.write(length); + + byte[] bytes = value.toByteArray(); + + if (bytes[0] == 0) + { + out.write(bytes, 1, bytes.length - 1); + } + else + { + out.write(bytes, 0, bytes.length); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/MarkerPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/MarkerPacket.java new file mode 100644 index 000000000..e4efbe85c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/MarkerPacket.java @@ -0,0 +1,28 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +/** + * Basic type for a marker packet + */ +public class MarkerPacket + extends ContainedPacket +{ + // "PGP" + + byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 }; + + public MarkerPacket( + BCPGInputStream in) + throws IOException + { + in.readFully(marker); + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(MARKER, marker, true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ModDetectionCodePacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ModDetectionCodePacket.java new file mode 100644 index 000000000..70bf4d27f --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/ModDetectionCodePacket.java @@ -0,0 +1,45 @@ +package org.spongycastle.bcpg; + +import java.io.*; + +/** + * basic packet for a modification detection code packet. + */ +public class ModDetectionCodePacket + extends ContainedPacket +{ + private byte[] digest; + + ModDetectionCodePacket( + BCPGInputStream in) + throws IOException + { + this.digest = new byte[20]; + in.readFully(this.digest); + } + + public ModDetectionCodePacket( + byte[] digest) + throws IOException + { + this.digest = new byte[digest.length]; + + System.arraycopy(digest, 0, this.digest, 0, this.digest.length); + } + + public byte[] getDigest() + { + byte[] tmp = new byte[digest.length]; + + System.arraycopy(digest, 0, tmp, 0, tmp.length); + + return tmp; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(MOD_DETECTION_CODE, digest, false); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/OnePassSignaturePacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/OnePassSignaturePacket.java new file mode 100644 index 000000000..59d73160d --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/OnePassSignaturePacket.java @@ -0,0 +1,115 @@ +package org.spongycastle.bcpg; + +import java.io.*; + +/** + * generic signature object + */ +public class OnePassSignaturePacket + extends ContainedPacket +{ + private int version; + private int sigType; + private int hashAlgorithm; + private int keyAlgorithm; + private long keyID; + private int nested; + + OnePassSignaturePacket( + BCPGInputStream in) + throws IOException + { + version = in.read(); + sigType = in.read(); + hashAlgorithm = in.read(); + keyAlgorithm = in.read(); + + keyID |= (long)in.read() << 56; + keyID |= (long)in.read() << 48; + keyID |= (long)in.read() << 40; + keyID |= (long)in.read() << 32; + keyID |= (long)in.read() << 24; + keyID |= (long)in.read() << 16; + keyID |= (long)in.read() << 8; + keyID |= in.read(); + + nested = in.read(); + } + + public OnePassSignaturePacket( + int sigType, + int hashAlgorithm, + int keyAlgorithm, + long keyID, + boolean isNested) + { + this.version = 3; + this.sigType = sigType; + this.hashAlgorithm = hashAlgorithm; + this.keyAlgorithm = keyAlgorithm; + this.keyID = keyID; + this.nested = (isNested) ? 0 : 1; + } + + /** + * Return the signature type. + * @return the signature type + */ + public int getSignatureType() + { + return sigType; + } + + /** + * return the encryption algorithm tag + */ + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + /** + * return the hashAlgorithm tag + */ + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + /** + * @return long + */ + public long getKeyID() + { + return keyID; + } + + /** + * + */ + public void encode( + BCPGOutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.write(version); + pOut.write(sigType); + pOut.write(hashAlgorithm); + pOut.write(keyAlgorithm); + + pOut.write((byte)(keyID >> 56)); + pOut.write((byte)(keyID >> 48)); + pOut.write((byte)(keyID >> 40)); + pOut.write((byte)(keyID >> 32)); + pOut.write((byte)(keyID >> 24)); + pOut.write((byte)(keyID >> 16)); + pOut.write((byte)(keyID >> 8)); + pOut.write((byte)(keyID)); + + pOut.write(nested); + + out.writePacket(ONE_PASS_SIGNATURE, bOut.toByteArray(), true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/OutputStreamPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/OutputStreamPacket.java new file mode 100644 index 000000000..4575ee05b --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/OutputStreamPacket.java @@ -0,0 +1,18 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +public abstract class OutputStreamPacket +{ + protected BCPGOutputStream out; + + public OutputStreamPacket( + BCPGOutputStream out) + { + this.out = out; + } + + public abstract BCPGOutputStream open() throws IOException; + + public abstract void close() throws IOException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/Packet.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/Packet.java new file mode 100644 index 000000000..6fcc840ea --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/Packet.java @@ -0,0 +1,9 @@ +package org.spongycastle.bcpg; + +/** + */ +public class Packet + implements PacketTags +{ + +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PacketTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PacketTags.java new file mode 100644 index 000000000..d20efcef9 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PacketTags.java @@ -0,0 +1,31 @@ +package org.spongycastle.bcpg; + +/** + * Basic PGP packet tag types. + */ +public interface PacketTags +{ + public static final int RESERVED = 0 ; // Reserved - a packet tag must not have this value + public static final int PUBLIC_KEY_ENC_SESSION = 1; // Public-Key Encrypted Session Key Packet + public static final int SIGNATURE = 2; // Signature Packet + public static final int SYMMETRIC_KEY_ENC_SESSION = 3; // Symmetric-Key Encrypted Session Key Packet + public static final int ONE_PASS_SIGNATURE = 4 ; // One-Pass Signature Packet + public static final int SECRET_KEY = 5; // Secret Key Packet + public static final int PUBLIC_KEY = 6 ; // Public Key Packet + public static final int SECRET_SUBKEY = 7; // Secret Subkey Packet + public static final int COMPRESSED_DATA = 8; // Compressed Data Packet + public static final int SYMMETRIC_KEY_ENC = 9; // Symmetrically Encrypted Data Packet + public static final int MARKER = 10; // Marker Packet + public static final int LITERAL_DATA = 11; // Literal Data Packet + public static final int TRUST = 12; // Trust Packet + public static final int USER_ID = 13; // User ID Packet + public static final int PUBLIC_SUBKEY = 14; // Public Subkey Packet + public static final int USER_ATTRIBUTE = 17; // User attribute + public static final int SYM_ENC_INTEGRITY_PRO = 18; // Symmetric encrypted, integrity protected + public static final int MOD_DETECTION_CODE = 19; // Modification detection code + + public static final int EXPERIMENTAL_1 = 60; // Private or Experimental Values + public static final int EXPERIMENTAL_2 = 61; + public static final int EXPERIMENTAL_3 = 62; + public static final int EXPERIMENTAL_4 = 63; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyAlgorithmTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyAlgorithmTags.java new file mode 100644 index 000000000..fe1d8d950 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyAlgorithmTags.java @@ -0,0 +1,30 @@ +package org.spongycastle.bcpg; + +/** + * Public Key Algorithm tag numbers + */ +public interface PublicKeyAlgorithmTags +{ + public static final int RSA_GENERAL = 1; // RSA (Encrypt or Sign) + public static final int RSA_ENCRYPT = 2; // RSA Encrypt-Only + public static final int RSA_SIGN = 3; // RSA Sign-Only + public static final int ELGAMAL_ENCRYPT = 16; // Elgamal (Encrypt-Only), see [ELGAMAL] + public static final int DSA = 17; // DSA (Digital Signature Standard) + public static final int EC = 18; // Reserved for Elliptic Curve + public static final int ECDH = 18; // Reserved for Elliptic Curve (actual algorithm name) + public static final int ECDSA = 19; // Reserved for ECDSA + public static final int ELGAMAL_GENERAL = 20; // Elgamal (Encrypt or Sign) + public static final int DIFFIE_HELLMAN = 21; // Reserved for Diffie-Hellman (X9.42, as defined for IETF-S/MIME) + + public static final int EXPERIMENTAL_1 = 100; + public static final int EXPERIMENTAL_2 = 101; + public static final int EXPERIMENTAL_3 = 102; + public static final int EXPERIMENTAL_4 = 103; + public static final int EXPERIMENTAL_5 = 104; + public static final int EXPERIMENTAL_6 = 105; + public static final int EXPERIMENTAL_7 = 106; + public static final int EXPERIMENTAL_8 = 107; + public static final int EXPERIMENTAL_9 = 108; + public static final int EXPERIMENTAL_10 = 109; + public static final int EXPERIMENTAL_11 = 110; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyEncSessionPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyEncSessionPacket.java new file mode 100644 index 000000000..fcc9b31dd --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyEncSessionPacket.java @@ -0,0 +1,125 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.spongycastle.util.Arrays; +import org.spongycastle.util.io.Streams; + +/** + * basic packet for a PGP public key + */ +public class PublicKeyEncSessionPacket + extends ContainedPacket implements PublicKeyAlgorithmTags +{ + private int version; + private long keyID; + private int algorithm; + private byte[][] data; + + PublicKeyEncSessionPacket( + BCPGInputStream in) + throws IOException + { + version = in.read(); + + keyID |= (long)in.read() << 56; + keyID |= (long)in.read() << 48; + keyID |= (long)in.read() << 40; + keyID |= (long)in.read() << 32; + keyID |= (long)in.read() << 24; + keyID |= (long)in.read() << 16; + keyID |= (long)in.read() << 8; + keyID |= in.read(); + + algorithm = in.read(); + + switch (algorithm) + { + case RSA_ENCRYPT: + case RSA_GENERAL: + data = new byte[1][]; + + data[0] = new MPInteger(in).getEncoded(); + break; + case ELGAMAL_ENCRYPT: + case ELGAMAL_GENERAL: + data = new byte[2][]; + + data[0] = new MPInteger(in).getEncoded(); + data[1] = new MPInteger(in).getEncoded(); + break; + case ECDH: + data = new byte[1][]; + + data[0] = Streams.readAll(in); + break; + default: + throw new IOException("unknown PGP public key algorithm encountered"); + } + } + + public PublicKeyEncSessionPacket( + long keyID, + int algorithm, + byte[][] data) + { + this.version = 3; + this.keyID = keyID; + this.algorithm = algorithm; + this.data = new byte[data.length][]; + + for (int i = 0; i != data.length; i++) + { + this.data[i] = Arrays.clone(data[i]); + } + } + + public int getVersion() + { + return version; + } + + public long getKeyID() + { + return keyID; + } + + public int getAlgorithm() + { + return algorithm; + } + + public byte[][] getEncSessionKey() + { + return data; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.write(version); + + pOut.write((byte)(keyID >> 56)); + pOut.write((byte)(keyID >> 48)); + pOut.write((byte)(keyID >> 40)); + pOut.write((byte)(keyID >> 32)); + pOut.write((byte)(keyID >> 24)); + pOut.write((byte)(keyID >> 16)); + pOut.write((byte)(keyID >> 8)); + pOut.write((byte)(keyID)); + + pOut.write(algorithm); + + for (int i = 0; i != data.length; i++) + { + pOut.write(data[i]); + } + + out.writePacket(PUBLIC_KEY_ENC_SESSION , bOut.toByteArray(), true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyPacket.java new file mode 100644 index 000000000..ac1dc3ba1 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicKeyPacket.java @@ -0,0 +1,133 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Date; + +/** + * basic packet for a PGP public key + */ +public class PublicKeyPacket + extends ContainedPacket implements PublicKeyAlgorithmTags +{ + private int version; + private long time; + private int validDays; + private int algorithm; + private BCPGKey key; + + PublicKeyPacket( + BCPGInputStream in) + throws IOException + { + version = in.read(); + time = ((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read(); + + if (version <= 3) + { + validDays = (in.read() << 8) | in.read(); + } + + algorithm = (byte)in.read(); + + switch (algorithm) + { + case RSA_ENCRYPT: + case RSA_GENERAL: + case RSA_SIGN: + key = new RSAPublicBCPGKey(in); + break; + case DSA: + key = new DSAPublicBCPGKey(in); + break; + case ELGAMAL_ENCRYPT: + case ELGAMAL_GENERAL: + key = new ElGamalPublicBCPGKey(in); + break; + case EC: + key = new ECDHPublicBCPGKey(in); + break; + case ECDSA: + key = new ECDSAPublicBCPGKey(in); + break; + default: + throw new IOException("unknown PGP public key algorithm encountered"); + } + } + + /** + * Construct version 4 public key packet. + * + * @param algorithm + * @param time + * @param key + */ + public PublicKeyPacket( + int algorithm, + Date time, + BCPGKey key) + { + this.version = 4; + this.time = time.getTime() / 1000; + this.algorithm = algorithm; + this.key = key; + } + + public int getVersion() + { + return version; + } + + public int getAlgorithm() + { + return algorithm; + } + + public int getValidDays() + { + return validDays; + } + + public Date getTime() + { + return new Date(time * 1000); + } + + public BCPGKey getKey() + { + return key; + } + + public byte[] getEncodedContents() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.write(version); + + pOut.write((byte)(time >> 24)); + pOut.write((byte)(time >> 16)); + pOut.write((byte)(time >> 8)); + pOut.write((byte)time); + + if (version <= 3) + { + pOut.write((byte)(validDays >> 8)); + pOut.write((byte)validDays); + } + + pOut.write(algorithm); + + pOut.writeObject((BCPGObject)key); + + return bOut.toByteArray(); + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(PUBLIC_KEY, getEncodedContents(), true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicSubkeyPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicSubkeyPacket.java new file mode 100644 index 000000000..5e745ef46 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/PublicSubkeyPacket.java @@ -0,0 +1,40 @@ +package org.spongycastle.bcpg; + +import java.io.*; +import java.util.Date; + +/** + * basic packet for a PGP public key + */ +public class PublicSubkeyPacket + extends PublicKeyPacket +{ + PublicSubkeyPacket( + BCPGInputStream in) + throws IOException + { + super(in); + } + + /** + * Construct version 4 public key packet. + * + * @param algorithm + * @param time + * @param key + */ + public PublicSubkeyPacket( + int algorithm, + Date time, + BCPGKey key) + { + super(algorithm, time, key); + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(PUBLIC_SUBKEY, getEncodedContents(), true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/RSAPublicBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/RSAPublicBCPGKey.java new file mode 100644 index 000000000..e6eed70f3 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/RSAPublicBCPGKey.java @@ -0,0 +1,91 @@ +package org.spongycastle.bcpg; + +import java.math.BigInteger; +import java.io.*; + +/** + * base class for an RSA Public Key. + */ +public class RSAPublicBCPGKey + extends BCPGObject implements BCPGKey +{ + MPInteger n; + MPInteger e; + + /** + * Construct an RSA public key from the passed in stream. + * + * @param in + * @throws IOException + */ + public RSAPublicBCPGKey( + BCPGInputStream in) + throws IOException + { + this.n = new MPInteger(in); + this.e = new MPInteger(in); + } + + /** + * + * @param n the modulus + * @param e the public exponent + */ + public RSAPublicBCPGKey( + BigInteger n, + BigInteger e) + { + this.n = new MPInteger(n); + this.e = new MPInteger(e); + } + + public BigInteger getPublicExponent() + { + return e.getValue(); + } + + public BigInteger getModulus() + { + return n.getValue(); + } + + /** + * return "PGP" + * + * @see org.spongycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see org.spongycastle.bcpg.BCPGKey#getEncoded() + */ + public byte[] getEncoded() + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pgpOut = new BCPGOutputStream(bOut); + + pgpOut.writeObject(this); + + return bOut.toByteArray(); + } + catch (IOException e) + { + return null; + } + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writeObject(n); + out.writeObject(e); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/RSASecretBCPGKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/RSASecretBCPGKey.java new file mode 100644 index 000000000..a8b211673 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/RSASecretBCPGKey.java @@ -0,0 +1,176 @@ +package org.spongycastle.bcpg; + +import java.io.*; +import java.math.BigInteger; + +/** + * base class for an RSA Secret (or Private) Key. + */ +public class RSASecretBCPGKey + extends BCPGObject implements BCPGKey +{ + MPInteger d; + MPInteger p; + MPInteger q; + MPInteger u; + + BigInteger expP, expQ, crt; + + /** + * + * @param in + * @throws IOException + */ + public RSASecretBCPGKey( + BCPGInputStream in) + throws IOException + { + this.d = new MPInteger(in); + this.p = new MPInteger(in); + this.q = new MPInteger(in); + this.u = new MPInteger(in); + + expP = d.getValue().remainder(p.getValue().subtract(BigInteger.valueOf(1))); + expQ = d.getValue().remainder(q.getValue().subtract(BigInteger.valueOf(1))); + crt = q.getValue().modInverse(p.getValue()); + } + + /** + * + * @param d + * @param p + * @param q + */ + public RSASecretBCPGKey( + BigInteger d, + BigInteger p, + BigInteger q) + { + // + // pgp requires (p < q) + // + int cmp = p.compareTo(q); + if (cmp >= 0) + { + if (cmp == 0) + { + throw new IllegalArgumentException("p and q cannot be equal"); + } + + BigInteger tmp = p; + p = q; + q = tmp; + } + + this.d = new MPInteger(d); + this.p = new MPInteger(p); + this.q = new MPInteger(q); + this.u = new MPInteger(p.modInverse(q)); + + expP = d.remainder(p.subtract(BigInteger.valueOf(1))); + expQ = d.remainder(q.subtract(BigInteger.valueOf(1))); + crt = q.modInverse(p); + } + + /** + * return the modulus for this key. + * + * @return BigInteger + */ + public BigInteger getModulus() + { + return p.getValue().multiply(q.getValue()); + } + + /** + * return the private exponent for this key. + * + * @return BigInteger + */ + public BigInteger getPrivateExponent() + { + return d.getValue(); + } + + /** + * return the prime P + */ + public BigInteger getPrimeP() + { + return p.getValue(); + } + + /** + * return the prime Q + */ + public BigInteger getPrimeQ() + { + return q.getValue(); + } + + /** + * return the prime exponent of p + */ + public BigInteger getPrimeExponentP() + { + return expP; + } + + /** + * return the prime exponent of q + */ + public BigInteger getPrimeExponentQ() + { + return expQ; + } + + /** + * return the crt coefficient + */ + public BigInteger getCrtCoefficient() + { + return crt; + } + + /** + * return "PGP" + * + * @see org.spongycastle.bcpg.BCPGKey#getFormat() + */ + public String getFormat() + { + return "PGP"; + } + + /** + * return the standard PGP encoding of the key. + * + * @see org.spongycastle.bcpg.BCPGKey#getEncoded() + */ + public byte[] getEncoded() + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pgpOut = new BCPGOutputStream(bOut); + + pgpOut.writeObject(this); + + return bOut.toByteArray(); + } + catch (IOException e) + { + return null; + } + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writeObject(d); + out.writeObject(p); + out.writeObject(q); + out.writeObject(u); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/S2K.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/S2K.java new file mode 100644 index 000000000..50bf2c4e2 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/S2K.java @@ -0,0 +1,151 @@ +package org.spongycastle.bcpg; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * The string to key specifier class + */ +public class S2K + extends BCPGObject +{ + private static final int EXPBIAS = 6; + + public static final int SIMPLE = 0; + public static final int SALTED = 1; + public static final int SALTED_AND_ITERATED = 3; + public static final int GNU_DUMMY_S2K = 101; + + int type; + int algorithm; + byte[] iv; + int itCount = -1; + int protectionMode = -1; + + S2K( + InputStream in) + throws IOException + { + DataInputStream dIn = new DataInputStream(in); + + type = dIn.read(); + algorithm = dIn.read(); + + // + // if this happens we have a dummy-S2K packet. + // + if (type != GNU_DUMMY_S2K) + { + if (type != 0) + { + iv = new byte[8]; + dIn.readFully(iv, 0, iv.length); + + if (type == 3) + { + itCount = dIn.read(); + } + } + } + else + { + dIn.read(); // G + dIn.read(); // N + dIn.read(); // U + protectionMode = dIn.read(); // protection mode + } + } + + public S2K( + int algorithm) + { + this.type = 0; + this.algorithm = algorithm; + } + + public S2K( + int algorithm, + byte[] iv) + { + this.type = 1; + this.algorithm = algorithm; + this.iv = iv; + } + + public S2K( + int algorithm, + byte[] iv, + int itCount) + { + this.type = 3; + this.algorithm = algorithm; + this.iv = iv; + this.itCount = itCount; + } + + public int getType() + { + return type; + } + + /** + * return the hash algorithm for this S2K + */ + public int getHashAlgorithm() + { + return algorithm; + } + + /** + * return the iv for the key generation algorithm + */ + public byte[] getIV() + { + return iv; + } + + /** + * return the iteration count + */ + public long getIterationCount() + { + return (16 + (itCount & 15)) << ((itCount >> 4) + EXPBIAS); + } + + /** + * the protection mode - only if GNU_DUMMY_S2K + */ + public int getProtectionMode() + { + return protectionMode; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.write(type); + out.write(algorithm); + + if (type != GNU_DUMMY_S2K) + { + if (type != 0) + { + out.write(iv); + } + + if (type == 3) + { + out.write(itCount); + } + } + else + { + out.write('G'); + out.write('N'); + out.write('U'); + out.write(protectionMode); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SecretKeyPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SecretKeyPacket.java new file mode 100644 index 000000000..e5e72c39e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SecretKeyPacket.java @@ -0,0 +1,185 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * basic packet for a PGP secret key + */ +public class SecretKeyPacket + extends ContainedPacket implements PublicKeyAlgorithmTags +{ + public static final int USAGE_NONE = 0x00; + public static final int USAGE_CHECKSUM = 0xff; + public static final int USAGE_SHA1 = 0xfe; + + private PublicKeyPacket pubKeyPacket; + private byte[] secKeyData; + private int s2kUsage; + private int encAlgorithm; + private S2K s2k; + private byte[] iv; + + /** + * + * @param in + * @throws IOException + */ + SecretKeyPacket( + BCPGInputStream in) + throws IOException + { + if (this instanceof SecretSubkeyPacket) + { + pubKeyPacket = new PublicSubkeyPacket(in); + } + else + { + pubKeyPacket = new PublicKeyPacket(in); + } + + s2kUsage = in.read(); + + if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1) + { + encAlgorithm = in.read(); + s2k = new S2K(in); + } + else + { + encAlgorithm = s2kUsage; + } + + if (!(s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K && s2k.getProtectionMode() == 0x01)) + { + if (s2kUsage != 0) + { + if (encAlgorithm < 7) + { + iv = new byte[8]; + } + else + { + iv = new byte[16]; + } + in.readFully(iv, 0, iv.length); + } + } + + this.secKeyData = in.readAll(); + } + + /** + * + * @param pubKeyPacket + * @param encAlgorithm + * @param s2k + * @param iv + * @param secKeyData + */ + public SecretKeyPacket( + PublicKeyPacket pubKeyPacket, + int encAlgorithm, + S2K s2k, + byte[] iv, + byte[] secKeyData) + { + this.pubKeyPacket = pubKeyPacket; + this.encAlgorithm = encAlgorithm; + + if (encAlgorithm != SymmetricKeyAlgorithmTags.NULL) + { + this.s2kUsage = USAGE_CHECKSUM; + } + else + { + this.s2kUsage = USAGE_NONE; + } + + this.s2k = s2k; + this.iv = iv; + this.secKeyData = secKeyData; + } + + public SecretKeyPacket( + PublicKeyPacket pubKeyPacket, + int encAlgorithm, + int s2kUsage, + S2K s2k, + byte[] iv, + byte[] secKeyData) + { + this.pubKeyPacket = pubKeyPacket; + this.encAlgorithm = encAlgorithm; + this.s2kUsage = s2kUsage; + this.s2k = s2k; + this.iv = iv; + this.secKeyData = secKeyData; + } + + public int getEncAlgorithm() + { + return encAlgorithm; + } + + public int getS2KUsage() + { + return s2kUsage; + } + + public byte[] getIV() + { + return iv; + } + + public S2K getS2K() + { + return s2k; + } + + public PublicKeyPacket getPublicKeyPacket() + { + return pubKeyPacket; + } + + public byte[] getSecretKeyData() + { + return secKeyData; + } + + public byte[] getEncodedContents() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.write(pubKeyPacket.getEncodedContents()); + + pOut.write(s2kUsage); + + if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1) + { + pOut.write(encAlgorithm); + pOut.writeObject(s2k); + } + + if (iv != null) + { + pOut.write(iv); + } + + if (secKeyData != null && secKeyData.length > 0) + { + pOut.write(secKeyData); + } + + return bOut.toByteArray(); + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(SECRET_KEY, getEncodedContents(), true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SecretSubkeyPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SecretSubkeyPacket.java new file mode 100644 index 000000000..d978d5c42 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SecretSubkeyPacket.java @@ -0,0 +1,58 @@ +package org.spongycastle.bcpg; + +import java.io.*; + +/** + * basic packet for a PGP secret key + */ +public class SecretSubkeyPacket + extends SecretKeyPacket +{ + /** + * + * @param in + * @throws IOException + */ + SecretSubkeyPacket( + BCPGInputStream in) + throws IOException + { + super(in); + } + + /** + * + * @param pubKeyPacket + * @param encAlgorithm + * @param s2k + * @param iv + * @param secKeyData + */ + public SecretSubkeyPacket( + PublicKeyPacket pubKeyPacket, + int encAlgorithm, + S2K s2k, + byte[] iv, + byte[] secKeyData) + { + super(pubKeyPacket, encAlgorithm, s2k, iv, secKeyData); + } + + public SecretSubkeyPacket( + PublicKeyPacket pubKeyPacket, + int encAlgorithm, + int s2kUsage, + S2K s2k, + byte[] iv, + byte[] secKeyData) + { + super(pubKeyPacket, encAlgorithm, s2kUsage, s2k, iv, secKeyData); + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(SECRET_SUBKEY, getEncodedContents(), true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignaturePacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignaturePacket.java new file mode 100644 index 000000000..38c06ccb5 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignaturePacket.java @@ -0,0 +1,523 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Vector; + +import org.spongycastle.bcpg.sig.IssuerKeyID; +import org.spongycastle.bcpg.sig.SignatureCreationTime; +import org.spongycastle.util.Arrays; + +/** + * generic signature packet + */ +public class SignaturePacket + extends ContainedPacket implements PublicKeyAlgorithmTags +{ + private int version; + private int signatureType; + private long creationTime; + private long keyID; + private int keyAlgorithm; + private int hashAlgorithm; + private MPInteger[] signature; + private byte[] fingerPrint; + private SignatureSubpacket[] hashedData; + private SignatureSubpacket[] unhashedData; + private byte[] signatureEncoding; + + SignaturePacket( + BCPGInputStream in) + throws IOException + { + version = in.read(); + + if (version == 3 || version == 2) + { + int l = in.read(); + + signatureType = in.read(); + creationTime = (((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read()) * 1000; + keyID |= (long)in.read() << 56; + keyID |= (long)in.read() << 48; + keyID |= (long)in.read() << 40; + keyID |= (long)in.read() << 32; + keyID |= (long)in.read() << 24; + keyID |= (long)in.read() << 16; + keyID |= (long)in.read() << 8; + keyID |= in.read(); + keyAlgorithm = in.read(); + hashAlgorithm = in.read(); + } + else if (version == 4) + { + signatureType = in.read(); + keyAlgorithm = in.read(); + hashAlgorithm = in.read(); + + int hashedLength = (in.read() << 8) | in.read(); + byte[] hashed = new byte[hashedLength]; + + in.readFully(hashed); + + // + // read the signature sub packet data. + // + SignatureSubpacket sub; + SignatureSubpacketInputStream sIn = new SignatureSubpacketInputStream( + new ByteArrayInputStream(hashed)); + + Vector v = new Vector(); + while ((sub = sIn.readPacket()) != null) + { + v.addElement(sub); + } + + hashedData = new SignatureSubpacket[v.size()]; + + for (int i = 0; i != hashedData.length; i++) + { + SignatureSubpacket p = (SignatureSubpacket)v.elementAt(i); + if (p instanceof IssuerKeyID) + { + keyID = ((IssuerKeyID)p).getKeyID(); + } + else if (p instanceof SignatureCreationTime) + { + creationTime = ((SignatureCreationTime)p).getTime().getTime(); + } + + hashedData[i] = p; + } + + int unhashedLength = (in.read() << 8) | in.read(); + byte[] unhashed = new byte[unhashedLength]; + + in.readFully(unhashed); + + sIn = new SignatureSubpacketInputStream( + new ByteArrayInputStream(unhashed)); + + v.removeAllElements(); + while ((sub = sIn.readPacket()) != null) + { + v.addElement(sub); + } + + unhashedData = new SignatureSubpacket[v.size()]; + + for (int i = 0; i != unhashedData.length; i++) + { + SignatureSubpacket p = (SignatureSubpacket)v.elementAt(i); + if (p instanceof IssuerKeyID) + { + keyID = ((IssuerKeyID)p).getKeyID(); + } + + unhashedData[i] = p; + } + } + else + { + throw new RuntimeException("unsupported version: " + version); + } + + fingerPrint = new byte[2]; + in.readFully(fingerPrint); + + switch (keyAlgorithm) + { + case RSA_GENERAL: + case RSA_SIGN: + MPInteger v = new MPInteger(in); + + signature = new MPInteger[1]; + signature[0] = v; + break; + case DSA: + MPInteger r = new MPInteger(in); + MPInteger s = new MPInteger(in); + + signature = new MPInteger[2]; + signature[0] = r; + signature[1] = s; + break; + case ELGAMAL_ENCRYPT: // yep, this really does happen sometimes. + case ELGAMAL_GENERAL: + MPInteger p = new MPInteger(in); + MPInteger g = new MPInteger(in); + MPInteger y = new MPInteger(in); + + signature = new MPInteger[3]; + signature[0] = p; + signature[1] = g; + signature[2] = y; + break; + case ECDSA: + MPInteger ecR = new MPInteger(in); + MPInteger ecS = new MPInteger(in); + + signature = new MPInteger[2]; + signature[0] = ecR; + signature[1] = ecS; + break; + default: + if (keyAlgorithm >= PublicKeyAlgorithmTags.EXPERIMENTAL_1 && keyAlgorithm <= PublicKeyAlgorithmTags.EXPERIMENTAL_11) + { + signature = null; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + int ch; + while ((ch = in.read()) >= 0) + { + bOut.write(ch); + } + signatureEncoding = bOut.toByteArray(); + } + else + { + throw new IOException("unknown signature key algorithm: " + keyAlgorithm); + } + } + } + + /** + * Generate a version 4 signature packet. + * + * @param signatureType + * @param keyAlgorithm + * @param hashAlgorithm + * @param hashedData + * @param unhashedData + * @param fingerPrint + * @param signature + */ + public SignaturePacket( + int signatureType, + long keyID, + int keyAlgorithm, + int hashAlgorithm, + SignatureSubpacket[] hashedData, + SignatureSubpacket[] unhashedData, + byte[] fingerPrint, + MPInteger[] signature) + { + this(4, signatureType, keyID, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerPrint, signature); + } + + /** + * Generate a version 2/3 signature packet. + * + * @param signatureType + * @param keyAlgorithm + * @param hashAlgorithm + * @param fingerPrint + * @param signature + */ + public SignaturePacket( + int version, + int signatureType, + long keyID, + int keyAlgorithm, + int hashAlgorithm, + long creationTime, + byte[] fingerPrint, + MPInteger[] signature) + { + this(version, signatureType, keyID, keyAlgorithm, hashAlgorithm, null, null, fingerPrint, signature); + + this.creationTime = creationTime; + } + + public SignaturePacket( + int version, + int signatureType, + long keyID, + int keyAlgorithm, + int hashAlgorithm, + SignatureSubpacket[] hashedData, + SignatureSubpacket[] unhashedData, + byte[] fingerPrint, + MPInteger[] signature) + { + this.version = version; + this.signatureType = signatureType; + this.keyID = keyID; + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + this.hashedData = hashedData; + this.unhashedData = unhashedData; + this.fingerPrint = fingerPrint; + this.signature = signature; + + if (hashedData != null) + { + setCreationTime(); + } + } + + /** + * get the version number + */ + public int getVersion() + { + return version; + } + + /** + * return the signature type. + */ + public int getSignatureType() + { + return signatureType; + } + + /** + * return the keyID + * @return the keyID that created the signature. + */ + public long getKeyID() + { + return keyID; + } + + /** + * return the signature trailer that must be included with the data + * to reconstruct the signature + * + * @return byte[] + */ + public byte[] getSignatureTrailer() + { + byte[] trailer = null; + + if (version == 3 || version == 2) + { + trailer = new byte[5]; + + long time = creationTime / 1000; + + trailer[0] = (byte)signatureType; + trailer[1] = (byte)(time >> 24); + trailer[2] = (byte)(time >> 16); + trailer[3] = (byte)(time >> 8); + trailer[4] = (byte)(time); + } + else + { + ByteArrayOutputStream sOut = new ByteArrayOutputStream(); + + try + { + sOut.write((byte)this.getVersion()); + sOut.write((byte)this.getSignatureType()); + sOut.write((byte)this.getKeyAlgorithm()); + sOut.write((byte)this.getHashAlgorithm()); + + ByteArrayOutputStream hOut = new ByteArrayOutputStream(); + SignatureSubpacket[] hashed = this.getHashedSubPackets(); + + for (int i = 0; i != hashed.length; i++) + { + hashed[i].encode(hOut); + } + + byte[] data = hOut.toByteArray(); + + sOut.write((byte)(data.length >> 8)); + sOut.write((byte)data.length); + sOut.write(data); + + byte[] hData = sOut.toByteArray(); + + sOut.write((byte)this.getVersion()); + sOut.write((byte)0xff); + sOut.write((byte)(hData.length>> 24)); + sOut.write((byte)(hData.length >> 16)); + sOut.write((byte)(hData.length >> 8)); + sOut.write((byte)(hData.length)); + } + catch (IOException e) + { + throw new RuntimeException("exception generating trailer: " + e); + } + + trailer = sOut.toByteArray(); + } + + return trailer; + } + + /** + * return the encryption algorithm tag + */ + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + /** + * return the hashAlgorithm tag + */ + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + /** + * return the signature as a set of integers - note this is normalised to be the + * ASN.1 encoding of what appears in the signature packet. + */ + public MPInteger[] getSignature() + { + return signature; + } + + /** + * Return the byte encoding of the signature section. + * @return uninterpreted signature bytes. + */ + public byte[] getSignatureBytes() + { + if (signatureEncoding == null) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream bcOut = new BCPGOutputStream(bOut); + + for (int i = 0; i != signature.length; i++) + { + try + { + bcOut.writeObject(signature[i]); + } + catch (IOException e) + { + throw new RuntimeException("internal error: " + e); + } + } + return bOut.toByteArray(); + } + else + { + return Arrays.clone(signatureEncoding); + } + } + public SignatureSubpacket[] getHashedSubPackets() + { + return hashedData; + } + + public SignatureSubpacket[] getUnhashedSubPackets() + { + return unhashedData; + } + + /** + * Return the creation time of the signature in milli-seconds. + * + * @return the creation time in millis + */ + public long getCreationTime() + { + return creationTime; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.write(version); + + if (version == 3 || version == 2) + { + pOut.write(5); // the length of the next block + + long time = creationTime / 1000; + + pOut.write(signatureType); + pOut.write((byte)(time >> 24)); + pOut.write((byte)(time >> 16)); + pOut.write((byte)(time >> 8)); + pOut.write((byte)time); + + pOut.write((byte)(keyID >> 56)); + pOut.write((byte)(keyID >> 48)); + pOut.write((byte)(keyID >> 40)); + pOut.write((byte)(keyID >> 32)); + pOut.write((byte)(keyID >> 24)); + pOut.write((byte)(keyID >> 16)); + pOut.write((byte)(keyID >> 8)); + pOut.write((byte)(keyID)); + + pOut.write(keyAlgorithm); + pOut.write(hashAlgorithm); + } + else if (version == 4) + { + pOut.write(signatureType); + pOut.write(keyAlgorithm); + pOut.write(hashAlgorithm); + + ByteArrayOutputStream sOut = new ByteArrayOutputStream(); + + for (int i = 0; i != hashedData.length; i++) + { + hashedData[i].encode(sOut); + } + + byte[] data = sOut.toByteArray(); + + pOut.write(data.length >> 8); + pOut.write(data.length); + pOut.write(data); + + sOut.reset(); + + for (int i = 0; i != unhashedData.length; i++) + { + unhashedData[i].encode(sOut); + } + + data = sOut.toByteArray(); + + pOut.write(data.length >> 8); + pOut.write(data.length); + pOut.write(data); + } + else + { + throw new IOException("unknown version: " + version); + } + + pOut.write(fingerPrint); + + if (signature != null) + { + for (int i = 0; i != signature.length; i++) + { + pOut.writeObject(signature[i]); + } + } + else + { + pOut.write(signatureEncoding); + } + + out.writePacket(SIGNATURE, bOut.toByteArray(), true); + } + + private void setCreationTime() + { + for (int i = 0; i != hashedData.length; i++) + { + if (hashedData[i] instanceof SignatureCreationTime) + { + creationTime = ((SignatureCreationTime)hashedData[i]).getTime().getTime(); + break; + } + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacket.java new file mode 100644 index 000000000..c427cef94 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacket.java @@ -0,0 +1,81 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Basic type for a PGP Signature sub-packet. + */ +public class SignatureSubpacket +{ + int type; + boolean critical; + + protected byte[] data; + + protected SignatureSubpacket( + int type, + boolean critical, + byte[] data) + { + this.type = type; + this.critical = critical; + this.data = data; + } + + public int getType() + { + return type; + } + + public boolean isCritical() + { + return critical; + } + + /** + * return the generic data making up the packet. + */ + public byte[] getData() + { + return data; + } + + public void encode( + OutputStream out) + throws IOException + { + int bodyLen = data.length + 1; + + if (bodyLen < 192) + { + out.write((byte)bodyLen); + } + else if (bodyLen <= 8383) + { + bodyLen -= 192; + + out.write((byte)(((bodyLen >> 8) & 0xff) + 192)); + out.write((byte)bodyLen); + } + else + { + out.write(0xff); + out.write((byte)(bodyLen >> 24)); + out.write((byte)(bodyLen >> 16)); + out.write((byte)(bodyLen >> 8)); + out.write((byte)bodyLen); + } + + if (critical) + { + out.write(0x80 | type); + } + else + { + out.write(type); + } + + out.write(data); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacketInputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacketInputStream.java new file mode 100644 index 000000000..678062a58 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacketInputStream.java @@ -0,0 +1,159 @@ +package org.spongycastle.bcpg; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import org.spongycastle.bcpg.sig.Exportable; +import org.spongycastle.bcpg.sig.IssuerKeyID; +import org.spongycastle.bcpg.sig.KeyExpirationTime; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.bcpg.sig.NotationData; +import org.spongycastle.bcpg.sig.PreferredAlgorithms; +import org.spongycastle.bcpg.sig.PrimaryUserID; +import org.spongycastle.bcpg.sig.Revocable; +import org.spongycastle.bcpg.sig.SignatureCreationTime; +import org.spongycastle.bcpg.sig.SignatureExpirationTime; +import org.spongycastle.bcpg.sig.SignerUserID; +import org.spongycastle.bcpg.sig.TrustSignature; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.io.Streams; + +/** + * reader for signature sub-packets + */ +public class SignatureSubpacketInputStream + extends InputStream implements SignatureSubpacketTags +{ + InputStream in; + + public SignatureSubpacketInputStream( + InputStream in) + { + this.in = in; + } + + public int available() + throws IOException + { + return in.available(); + } + + public int read() + throws IOException + { + return in.read(); + } + + public SignatureSubpacket readPacket() + throws IOException + { + int l = this.read(); + int bodyLen = 0; + + if (l < 0) + { + return null; + } + + if (l < 192) + { + bodyLen = l; + } + else if (l <= 223) + { + bodyLen = ((l - 192) << 8) + (in.read()) + 192; + } + else if (l == 255) + { + bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read(); + } + else + { + // TODO Error? + } + + int tag = in.read(); + + if (tag < 0) + { + throw new EOFException("unexpected EOF reading signature sub packet"); + } + + byte[] data = new byte[bodyLen - 1]; + + // + // this may seem a bit strange but it turns out some applications miscode the length + // in fixed length fields, so we check the length we do get, only throwing an exception if + // we really cannot continue + // + int bytesRead = Streams.readFully(in, data); + + boolean isCritical = ((tag & 0x80) != 0); + int type = tag & 0x7f; + + if (bytesRead != data.length) + { + switch (type) + { + case CREATION_TIME: + data = checkData(data, 4, bytesRead, "Signature Creation Time"); + break; + case ISSUER_KEY_ID: + data = checkData(data, 8, bytesRead, "Issuer"); + break; + case KEY_EXPIRE_TIME: + data = checkData(data, 4, bytesRead, "Signature Key Expiration Time"); + break; + case EXPIRE_TIME: + data = checkData(data, 4, bytesRead, "Signature Expiration Time"); + break; + default: + throw new EOFException("truncated subpacket data."); + } + } + + switch (type) + { + case CREATION_TIME: + return new SignatureCreationTime(isCritical, data); + case KEY_EXPIRE_TIME: + return new KeyExpirationTime(isCritical, data); + case EXPIRE_TIME: + return new SignatureExpirationTime(isCritical, data); + case REVOCABLE: + return new Revocable(isCritical, data); + case EXPORTABLE: + return new Exportable(isCritical, data); + case ISSUER_KEY_ID: + return new IssuerKeyID(isCritical, data); + case TRUST_SIG: + return new TrustSignature(isCritical, data); + case PREFERRED_COMP_ALGS: + case PREFERRED_HASH_ALGS: + case PREFERRED_SYM_ALGS: + return new PreferredAlgorithms(type, isCritical, data); + case KEY_FLAGS: + return new KeyFlags(isCritical, data); + case PRIMARY_USER_ID: + return new PrimaryUserID(isCritical, data); + case SIGNER_USER_ID: + return new SignerUserID(isCritical, data); + case NOTATION_DATA: + return new NotationData(isCritical, data); + } + + return new SignatureSubpacket(type, isCritical, data); + } + + private byte[] checkData(byte[] data, int expected, int bytesRead, String name) + throws EOFException + { + if (bytesRead != expected) + { + throw new EOFException("truncated " + name + " subpacket data."); + } + + return Arrays.copyOfRange(data, 0, expected); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacketTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacketTags.java new file mode 100644 index 000000000..fdeaae52f --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SignatureSubpacketTags.java @@ -0,0 +1,32 @@ +package org.spongycastle.bcpg; + +/** + * Basic PGP signature sub-packet tag types. + */ +public interface SignatureSubpacketTags +{ + public static final int CREATION_TIME = 2; // signature creation time + public static final int EXPIRE_TIME = 3; // signature expiration time + public static final int EXPORTABLE = 4; // exportable certification + public static final int TRUST_SIG = 5; // trust signature + public static final int REG_EXP = 6; // regular expression + public static final int REVOCABLE = 7; // revocable + public static final int KEY_EXPIRE_TIME = 9; // key expiration time + public static final int PLACEHOLDER = 10; // placeholder for backward compatibility + public static final int PREFERRED_SYM_ALGS = 11; // preferred symmetric algorithms + public static final int REVOCATION_KEY = 12; // revocation key + public static final int ISSUER_KEY_ID = 16; // issuer key ID + public static final int NOTATION_DATA = 20; // notation data + public static final int PREFERRED_HASH_ALGS = 21; // preferred hash algorithms + public static final int PREFERRED_COMP_ALGS = 22; // preferred compression algorithms + public static final int KEY_SERVER_PREFS = 23; // key server preferences + public static final int PREFERRED_KEY_SERV = 24; // preferred key server + public static final int PRIMARY_USER_ID = 25; // primary user id + public static final int POLICY_URL = 26; // policy URL + public static final int KEY_FLAGS = 27; // key flags + public static final int SIGNER_USER_ID = 28; // signer's user id + public static final int REVOCATION_REASON = 29; // reason for revocation + public static final int FEATURES = 30; // features + public static final int SIGNATURE_TARGET = 31; // signature target + public static final int EMBEDDED_SIGNATURE = 32; // embedded signature +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricEncDataPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricEncDataPacket.java new file mode 100644 index 000000000..51cace695 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricEncDataPacket.java @@ -0,0 +1,14 @@ +package org.spongycastle.bcpg; + +/** + * Basic type for a symmetric key encrypted packet + */ +public class SymmetricEncDataPacket + extends InputStreamPacket +{ + public SymmetricEncDataPacket( + BCPGInputStream in) + { + super(in); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricEncIntegrityPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricEncIntegrityPacket.java new file mode 100644 index 000000000..aa9df0970 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricEncIntegrityPacket.java @@ -0,0 +1,20 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +/** + */ +public class SymmetricEncIntegrityPacket + extends InputStreamPacket +{ + int version; + + SymmetricEncIntegrityPacket( + BCPGInputStream in) + throws IOException + { + super(in); + + version = in.read(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricKeyAlgorithmTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricKeyAlgorithmTags.java new file mode 100644 index 000000000..4f3c7a2a1 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricKeyAlgorithmTags.java @@ -0,0 +1,19 @@ +package org.spongycastle.bcpg; + +/** + * Basic tags for symmetric key algorithms + */ +public interface SymmetricKeyAlgorithmTags +{ + public static final int NULL = 0; // Plaintext or unencrypted data + public static final int IDEA = 1; // IDEA [IDEA] + public static final int TRIPLE_DES = 2; // Triple-DES (DES-EDE, as per spec -168 bit key derived from 192) + public static final int CAST5 = 3; // CAST5 (128 bit key, as per RFC 2144) + public static final int BLOWFISH = 4; // Blowfish (128 bit key, 16 rounds) [BLOWFISH] + public static final int SAFER = 5; // SAFER-SK128 (13 rounds) [SAFER] + public static final int DES = 6; // Reserved for DES/SK + public static final int AES_128 = 7; // Reserved for AES with 128-bit key + public static final int AES_192 = 8; // Reserved for AES with 192-bit key + public static final int AES_256 = 9; // Reserved for AES with 256-bit key + public static final int TWOFISH = 10; // Reserved for Twofish +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricKeyEncSessionPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricKeyEncSessionPacket.java new file mode 100644 index 000000000..138bbba4c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/SymmetricKeyEncSessionPacket.java @@ -0,0 +1,90 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Basic type for a symmetric encrypted session key packet + */ +public class SymmetricKeyEncSessionPacket + extends ContainedPacket +{ + private int version; + private int encAlgorithm; + private S2K s2k; + private byte[] secKeyData; + + public SymmetricKeyEncSessionPacket( + BCPGInputStream in) + throws IOException + { + version = in.read(); + encAlgorithm = in.read(); + + s2k = new S2K(in); + + this.secKeyData = in.readAll(); + } + + public SymmetricKeyEncSessionPacket( + int encAlgorithm, + S2K s2k, + byte[] secKeyData) + { + this.version = 4; + this.encAlgorithm = encAlgorithm; + this.s2k = s2k; + this.secKeyData = secKeyData; + } + + /** + * @return int + */ + public int getEncAlgorithm() + { + return encAlgorithm; + } + + /** + * @return S2K + */ + public S2K getS2K() + { + return s2k; + } + + /** + * @return byte[] + */ + public byte[] getSecKeyData() + { + return secKeyData; + } + + /** + * @return int + */ + public int getVersion() + { + return version; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.write(version); + pOut.write(encAlgorithm); + pOut.writeObject(s2k); + + if (secKeyData != null && secKeyData.length > 0) + { + pOut.write(secKeyData); + } + + out.writePacket(SYMMETRIC_KEY_ENC_SESSION, bOut.toByteArray(), true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/TrustPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/TrustPacket.java new file mode 100644 index 000000000..3a98003be --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/TrustPacket.java @@ -0,0 +1,48 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Basic type for a trust packet + */ +public class TrustPacket + extends ContainedPacket +{ + byte[] levelAndTrustAmount; + + public TrustPacket( + BCPGInputStream in) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + int ch; + + while ((ch = in.read()) >= 0) + { + bOut.write(ch); + } + + levelAndTrustAmount = bOut.toByteArray(); + } + + public TrustPacket( + int trustCode) + { + this.levelAndTrustAmount = new byte[1]; + + this.levelAndTrustAmount[0] = (byte)trustCode; + } + + public byte[] getLevelAndTrustAmount() + { + return levelAndTrustAmount; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(TRUST, levelAndTrustAmount, true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributePacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributePacket.java new file mode 100644 index 000000000..64d3ca08a --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributePacket.java @@ -0,0 +1,60 @@ +package org.spongycastle.bcpg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Vector; + +/** + * Basic type for a user attribute packet. + */ +public class UserAttributePacket + extends ContainedPacket +{ + private UserAttributeSubpacket[] subpackets; + + public UserAttributePacket( + BCPGInputStream in) + throws IOException + { + UserAttributeSubpacketInputStream sIn = new UserAttributeSubpacketInputStream(in); + UserAttributeSubpacket sub; + + Vector v= new Vector(); + while ((sub = sIn.readPacket()) != null) + { + v.addElement(sub); + } + + subpackets = new UserAttributeSubpacket[v.size()]; + + for (int i = 0; i != subpackets.length; i++) + { + subpackets[i] = (UserAttributeSubpacket)v.elementAt(i); + } + } + + public UserAttributePacket( + UserAttributeSubpacket[] subpackets) + { + this.subpackets = subpackets; + } + + public UserAttributeSubpacket[] getSubpackets() + { + return subpackets; + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + for (int i = 0; i != subpackets.length; i++) + { + subpackets[i].encode(bOut); + } + + out.writePacket(USER_ATTRIBUTE, bOut.toByteArray(), false); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacket.java new file mode 100644 index 000000000..fabbc7a84 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacket.java @@ -0,0 +1,91 @@ +package org.spongycastle.bcpg; + +import org.spongycastle.util.Arrays; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Basic type for a user attribute sub-packet. + */ +public class UserAttributeSubpacket +{ + int type; + + protected byte[] data; + + protected UserAttributeSubpacket( + int type, + byte[] data) + { + this.type = type; + this.data = data; + } + + public int getType() + { + return type; + } + + /** + * return the generic data making up the packet. + */ + public byte[] getData() + { + return data; + } + + public void encode( + OutputStream out) + throws IOException + { + int bodyLen = data.length + 1; + + if (bodyLen < 192) + { + out.write((byte)bodyLen); + } + else if (bodyLen <= 8383) + { + bodyLen -= 192; + + out.write((byte)(((bodyLen >> 8) & 0xff) + 192)); + out.write((byte)bodyLen); + } + else + { + out.write(0xff); + out.write((byte)(bodyLen >> 24)); + out.write((byte)(bodyLen >> 16)); + out.write((byte)(bodyLen >> 8)); + out.write((byte)bodyLen); + } + + out.write(type); + out.write(data); + } + + public boolean equals( + Object o) + { + if (o == this) + { + return true; + } + + if (!(o instanceof UserAttributeSubpacket)) + { + return false; + } + + UserAttributeSubpacket other = (UserAttributeSubpacket)o; + + return this.type == other.type + && Arrays.areEqual(this.data, other.data); + } + + public int hashCode() + { + return type ^ Arrays.hashCode(data); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacketInputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacketInputStream.java new file mode 100644 index 000000000..ab55ea4e9 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacketInputStream.java @@ -0,0 +1,116 @@ +package org.spongycastle.bcpg; + +import java.io.*; + +import org.spongycastle.bcpg.attr.ImageAttribute; + +/** + * reader for user attribute sub-packets + */ +public class UserAttributeSubpacketInputStream + extends InputStream implements UserAttributeSubpacketTags +{ + InputStream in; + + public UserAttributeSubpacketInputStream( + InputStream in) + { + this.in = in; + } + + public int available() + throws IOException + { + return in.available(); + } + + public int read() + throws IOException + { + return in.read(); + } + + private void readFully( + byte[] buf, + int off, + int len) + throws IOException + { + if (len > 0) + { + int b = this.read(); + + if (b < 0) + { + throw new EOFException(); + } + + buf[off] = (byte)b; + off++; + len--; + } + + while (len > 0) + { + int l = in.read(buf, off, len); + + if (l < 0) + { + throw new EOFException(); + } + + off += l; + len -= l; + } + } + + public UserAttributeSubpacket readPacket() + throws IOException + { + int l = this.read(); + int bodyLen = 0; + + if (l < 0) + { + return null; + } + + if (l < 192) + { + bodyLen = l; + } + else if (l <= 223) + { + bodyLen = ((l - 192) << 8) + (in.read()) + 192; + } + else if (l == 255) + { + bodyLen = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read(); + } + else + { + // TODO Error? + } + + int tag = in.read(); + + if (tag < 0) + { + throw new EOFException("unexpected EOF reading user attribute sub packet"); + } + + byte[] data = new byte[bodyLen - 1]; + + this.readFully(data, 0, data.length); + + int type = tag; + + switch (type) + { + case IMAGE_ATTRIBUTE: + return new ImageAttribute(data); + } + + return new UserAttributeSubpacket(type, data); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacketTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacketTags.java new file mode 100644 index 000000000..fc653140a --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserAttributeSubpacketTags.java @@ -0,0 +1,9 @@ +package org.spongycastle.bcpg; + +/** + * Basic PGP user attribute sub-packet tag types. + */ +public interface UserAttributeSubpacketTags +{ + public static final int IMAGE_ATTRIBUTE = 1; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserIDPacket.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserIDPacket.java new file mode 100644 index 000000000..0a9d64428 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/UserIDPacket.java @@ -0,0 +1,39 @@ +package org.spongycastle.bcpg; + +import java.io.IOException; + +import org.spongycastle.util.Strings; + +/** + * Basic type for a user ID packet. + */ +public class UserIDPacket + extends ContainedPacket +{ + private byte[] idData; + + public UserIDPacket( + BCPGInputStream in) + throws IOException + { + this.idData = in.readAll(); + } + + public UserIDPacket( + String id) + { + this.idData = Strings.toUTF8ByteArray(id); + } + + public String getID() + { + return Strings.fromUTF8ByteArray(idData); + } + + public void encode( + BCPGOutputStream out) + throws IOException + { + out.writePacket(USER_ID, idData, true); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/attr/ImageAttribute.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/attr/ImageAttribute.java new file mode 100644 index 000000000..df48463f9 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/attr/ImageAttribute.java @@ -0,0 +1,77 @@ +package org.spongycastle.bcpg.attr; + +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.bcpg.UserAttributeSubpacketTags; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Basic type for a image attribute packet. + */ +public class ImageAttribute + extends UserAttributeSubpacket +{ + public static final int JPEG = 1; + + private static final byte[] ZEROES = new byte[12]; + + private int hdrLength; + private int version; + private int encoding; + private byte[] imageData; + + public ImageAttribute( + byte[] data) + { + super(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE, data); + + hdrLength = ((data[1] & 0xff) << 8) | (data[0] & 0xff); + version = data[2] & 0xff; + encoding = data[3] & 0xff; + + imageData = new byte[data.length - hdrLength]; + System.arraycopy(data, hdrLength, imageData, 0, imageData.length); + } + + public ImageAttribute( + int imageType, + byte[] imageData) + { + this(toByteArray(imageType, imageData)); + } + + private static byte[] toByteArray(int imageType, byte[] imageData) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + bOut.write(0x10); bOut.write(0x00); bOut.write(0x01); + bOut.write(imageType); + bOut.write(ZEROES); + bOut.write(imageData); + } + catch (IOException e) + { + throw new RuntimeException("unable to encode to byte array!"); + } + + return bOut.toByteArray(); + } + + public int version() + { + return version; + } + + public int getEncoding() + { + return encoding; + } + + public byte[] getImageData() + { + return imageData; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/EmbeddedSignature.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/EmbeddedSignature.java new file mode 100644 index 000000000..b4861b129 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/EmbeddedSignature.java @@ -0,0 +1,18 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * Packet embedded signature + */ +public class EmbeddedSignature + extends SignatureSubpacket +{ + public EmbeddedSignature( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.EMBEDDED_SIGNATURE, critical, data); + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Exportable.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Exportable.java new file mode 100644 index 000000000..5831a69a0 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Exportable.java @@ -0,0 +1,46 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving signature creation time. + */ +public class Exportable + extends SignatureSubpacket +{ + private static byte[] booleanToByteArray( + boolean value) + { + byte[] data = new byte[1]; + + if (value) + { + data[0] = 1; + return data; + } + else + { + return data; + } + } + + public Exportable( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.EXPORTABLE, critical, data); + } + + public Exportable( + boolean critical, + boolean isExportable) + { + super(SignatureSubpacketTags.EXPORTABLE, critical, booleanToByteArray(isExportable)); + } + + public boolean isExportable() + { + return data[0] != 0; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Features.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Features.java new file mode 100644 index 000000000..02ee57903 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Features.java @@ -0,0 +1,98 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +public class Features + extends SignatureSubpacket +{ + + /** Identifier for the modification detection feature */ + public static final byte FEATURE_MODIFICATION_DETECTION = 1; + + private static final byte[] featureToByteArray(byte feature) + { + byte[] data = new byte[1]; + data[0] = feature; + return data; + } + + public Features(boolean critical, byte[] data) + { + super(SignatureSubpacketTags.FEATURES, critical, data); + } + + public Features(boolean critical, byte feature) + { + super(SignatureSubpacketTags.FEATURES, critical, featureToByteArray(feature)); + } + + /** + * Returns if modification detection is supported. + */ + public boolean supportsModificationDetection() + { + return supportsFeature(FEATURE_MODIFICATION_DETECTION); + } + + +// /** Class should be immutable. +// * Set modification detection support. +// */ +// public void setSupportsModificationDetection(boolean support) +// { +// setSupportsFeature(FEATURE_MODIFICATION_DETECTION, support); +// } + + + /** + * Returns if a particular feature is supported. + */ + public boolean supportsFeature(byte feature) + { + for (int i = 0; i < data.length; i++) + { + if (data[i] == feature) + { + return true; + } + } + return false; + } + + + /** + * Sets support for a particular feature. + */ + private void setSupportsFeature(byte feature, boolean support) + { + if (feature == 0) + { + throw new IllegalArgumentException("feature == 0"); + } + if (supportsFeature(feature) != support) + { + if (support == true) + { + byte[] temp = new byte[data.length + 1]; + System.arraycopy(data, 0, temp, 0, data.length); + temp[data.length] = feature; + data = temp; + } + else + { + for (int i = 0; i < data.length; i++) + { + if (data[i] == feature) + { + byte[] temp = new byte[data.length - 1]; + System.arraycopy(data, 0, temp, 0, i); + System.arraycopy(data, i + 1, temp, i, temp.length - i); + data = temp; + break; + } + } + } + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/IssuerKeyID.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/IssuerKeyID.java new file mode 100644 index 000000000..b963b7974 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/IssuerKeyID.java @@ -0,0 +1,50 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving signature creation time. + */ +public class IssuerKeyID + extends SignatureSubpacket +{ + protected static byte[] keyIDToBytes( + long keyId) + { + byte[] data = new byte[8]; + + data[0] = (byte)(keyId >> 56); + data[1] = (byte)(keyId >> 48); + data[2] = (byte)(keyId >> 40); + data[3] = (byte)(keyId >> 32); + data[4] = (byte)(keyId >> 24); + data[5] = (byte)(keyId >> 16); + data[6] = (byte)(keyId >> 8); + data[7] = (byte)keyId; + + return data; + } + + public IssuerKeyID( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.ISSUER_KEY_ID, critical, data); + } + + public IssuerKeyID( + boolean critical, + long keyID) + { + super(SignatureSubpacketTags.ISSUER_KEY_ID, critical, keyIDToBytes(keyID)); + } + + public long getKeyID() + { + long keyID = ((long)(data[0] & 0xff) << 56) | ((long)(data[1] & 0xff) << 48) | ((long)(data[2] & 0xff) << 40) | ((long)(data[3] & 0xff) << 32) + | ((long)(data[4] & 0xff) << 24) | ((data[5] & 0xff) << 16) | ((data[6] & 0xff) << 8) | (data[7] & 0xff); + + return keyID; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/KeyExpirationTime.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/KeyExpirationTime.java new file mode 100644 index 000000000..dc0817c99 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/KeyExpirationTime.java @@ -0,0 +1,50 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving time after creation at which the key expires. + */ +public class KeyExpirationTime + extends SignatureSubpacket +{ + protected static byte[] timeToBytes( + long t) + { + byte[] data = new byte[4]; + + data[0] = (byte)(t >> 24); + data[1] = (byte)(t >> 16); + data[2] = (byte)(t >> 8); + data[3] = (byte)t; + + return data; + } + + public KeyExpirationTime( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.KEY_EXPIRE_TIME, critical, data); + } + + public KeyExpirationTime( + boolean critical, + long seconds) + { + super(SignatureSubpacketTags.KEY_EXPIRE_TIME, critical, timeToBytes(seconds)); + } + + /** + * Return the number of seconds after creation time a key is valid for. + * + * @return second count for key validity. + */ + public long getTime() + { + long time = ((long)(data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); + + return time; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/KeyFlags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/KeyFlags.java new file mode 100644 index 000000000..6ebb1fa87 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/KeyFlags.java @@ -0,0 +1,73 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * Packet holding the key flag values. + */ +public class KeyFlags + extends SignatureSubpacket +{ + public static final int CERTIFY_OTHER = 0x01; + public static final int SIGN_DATA = 0x02; + public static final int ENCRYPT_COMMS = 0x04; + public static final int ENCRYPT_STORAGE = 0x08; + public static final int SPLIT = 0x10; + public static final int AUTHENTICATION = 0x20; + public static final int SHARED = 0x80; + + private static byte[] intToByteArray( + int v) + { + byte[] tmp = new byte[4]; + int size = 0; + + for (int i = 0; i != 4; i++) + { + tmp[i] = (byte)(v >> (i * 8)); + if (tmp[i] != 0) + { + size = i; + } + } + + byte[] data = new byte[size + 1]; + + System.arraycopy(tmp, 0, data, 0, data.length); + + return data; + } + + public KeyFlags( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.KEY_FLAGS, critical, data); + } + + public KeyFlags( + boolean critical, + int flags) + { + super(SignatureSubpacketTags.KEY_FLAGS, critical, intToByteArray(flags)); + } + + /** + * Return the flag values contained in the first 4 octets (note: at the moment + * the standard only uses the first one). + * + * @return flag values. + */ + public int getFlags() + { + int flags = 0; + + for (int i = 0; i != data.length; i++) + { + flags |= (data[i] & 0xff) << (i * 8); + } + + return flags; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/NotationData.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/NotationData.java new file mode 100644 index 000000000..d4b04323b --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/NotationData.java @@ -0,0 +1,113 @@ +package org.spongycastle.bcpg.sig; + +import java.io.ByteArrayOutputStream; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.util.Strings; + +/** + * Class provided a NotationData object according to + * RFC2440, Chapter 5.2.3.15. Notation Data + */ +public class NotationData + extends SignatureSubpacket +{ + public static final int HEADER_FLAG_LENGTH = 4; + public static final int HEADER_NAME_LENGTH = 2; + public static final int HEADER_VALUE_LENGTH = 2; + + public NotationData(boolean critical, byte[] data) + { + super(SignatureSubpacketTags.NOTATION_DATA, critical, data); + } + + public NotationData( + boolean critical, + boolean humanReadable, + String notationName, + String notationValue) + { + super(SignatureSubpacketTags.NOTATION_DATA, critical, createData(humanReadable, notationName, notationValue)); + } + + private static byte[] createData(boolean humanReadable, String notationName, String notationValue) + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + +// (4 octets of flags, 2 octets of name length (M), +// 2 octets of value length (N), +// M octets of name data, +// N octets of value data) + + // flags + out.write(humanReadable ? 0x80 : 0x00); + out.write(0x0); + out.write(0x0); + out.write(0x0); + + byte[] nameData, valueData = null; + int nameLength, valueLength; + + nameData = Strings.toUTF8ByteArray(notationName); + nameLength = Math.min(nameData.length, 0xFFFF); + + if (nameLength != nameData.length) + { + throw new IllegalArgumentException("notationName exceeds maximum length."); + } + + valueData = Strings.toUTF8ByteArray(notationValue); + valueLength = Math.min(valueData.length, 0xFFFF); + if (valueLength != valueData.length) + { + throw new IllegalArgumentException("notationValue exceeds maximum length."); + } + + // name length + out.write((nameLength >>> 8) & 0xFF); + out.write((nameLength >>> 0) & 0xFF); + + // value length + out.write((valueLength >>> 8) & 0xFF); + out.write((valueLength >>> 0) & 0xFF); + + // name + out.write(nameData, 0, nameLength); + + // value + out.write(valueData, 0, valueLength); + + return out.toByteArray(); + } + + public boolean isHumanReadable() + { + return data[0] == (byte)0x80; + } + + public String getNotationName() + { + int nameLength = (((data[HEADER_FLAG_LENGTH] & 0xff) << 8) + (data[HEADER_FLAG_LENGTH + 1] & 0xff)); + + byte bName[] = new byte[nameLength]; + System.arraycopy(data, HEADER_FLAG_LENGTH + HEADER_NAME_LENGTH + HEADER_VALUE_LENGTH, bName, 0, nameLength); + + return Strings.fromUTF8ByteArray(bName); + } + + public String getNotationValue() + { + return Strings.fromUTF8ByteArray(getNotationValueBytes()); + } + + public byte[] getNotationValueBytes() + { + int nameLength = (((data[HEADER_FLAG_LENGTH] & 0xff) << 8) + (data[HEADER_FLAG_LENGTH + 1] & 0xff)); + int valueLength = (((data[HEADER_FLAG_LENGTH + HEADER_NAME_LENGTH] & 0xff) << 8) + (data[HEADER_FLAG_LENGTH + HEADER_NAME_LENGTH + 1] & 0xff)); + + byte bValue[] = new byte[valueLength]; + System.arraycopy(data, HEADER_FLAG_LENGTH + HEADER_NAME_LENGTH + HEADER_VALUE_LENGTH + nameLength, bValue, 0, valueLength); + return bValue; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/PreferredAlgorithms.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/PreferredAlgorithms.java new file mode 100644 index 000000000..ce99a1dc1 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/PreferredAlgorithms.java @@ -0,0 +1,59 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; + +/** + * packet giving signature creation time. + */ +public class PreferredAlgorithms + extends SignatureSubpacket +{ + private static byte[] intToByteArray( + int[] v) + { + byte[] data = new byte[v.length]; + + for (int i = 0; i != v.length; i++) + { + data[i] = (byte)v[i]; + } + + return data; + } + + public PreferredAlgorithms( + int type, + boolean critical, + byte[] data) + { + super(type, critical, data); + } + + public PreferredAlgorithms( + int type, + boolean critical, + int[] preferrences) + { + super(type, critical, intToByteArray(preferrences)); + } + + /** + * @deprecated mispelt! + */ + public int[] getPreferrences() + { + return getPreferences(); + } + + public int[] getPreferences() + { + int[] v = new int[data.length]; + + for (int i = 0; i != v.length; i++) + { + v[i] = data[i] & 0xff; + } + + return v; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/PrimaryUserID.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/PrimaryUserID.java new file mode 100644 index 000000000..80829ad88 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/PrimaryUserID.java @@ -0,0 +1,46 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving whether or not the signature is signed using the primary user ID for the key. + */ +public class PrimaryUserID + extends SignatureSubpacket +{ + private static byte[] booleanToByteArray( + boolean value) + { + byte[] data = new byte[1]; + + if (value) + { + data[0] = 1; + return data; + } + else + { + return data; + } + } + + public PrimaryUserID( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.PRIMARY_USER_ID, critical, data); + } + + public PrimaryUserID( + boolean critical, + boolean isPrimaryUserID) + { + super(SignatureSubpacketTags.PRIMARY_USER_ID, critical, booleanToByteArray(isPrimaryUserID)); + } + + public boolean isPrimaryUserID() + { + return data[0] != 0; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Revocable.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Revocable.java new file mode 100644 index 000000000..b982b0147 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/Revocable.java @@ -0,0 +1,46 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving whether or not is revocable. + */ +public class Revocable + extends SignatureSubpacket +{ + private static byte[] booleanToByteArray( + boolean value) + { + byte[] data = new byte[1]; + + if (value) + { + data[0] = 1; + return data; + } + else + { + return data; + } + } + + public Revocable( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.REVOCABLE, critical, data); + } + + public Revocable( + boolean critical, + boolean isRevocable) + { + super(SignatureSubpacketTags.REVOCABLE, critical, booleanToByteArray(isRevocable)); + } + + public boolean isRevocable() + { + return data[0] != 0; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationKey.java new file mode 100644 index 000000000..b10593303 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationKey.java @@ -0,0 +1,52 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * Represents revocation key OpenPGP signature sub packet. + */ +public class RevocationKey extends SignatureSubpacket +{ + // 1 octet of class, + // 1 octet of public-key algorithm ID, + // 20 octets of fingerprint + public RevocationKey(boolean isCritical, byte[] data) + { + super(SignatureSubpacketTags.REVOCATION_KEY, isCritical, data); + } + + public RevocationKey(boolean isCritical, byte signatureClass, int keyAlgorithm, + byte[] fingerprint) + { + super(SignatureSubpacketTags.REVOCATION_KEY, isCritical, createData(signatureClass, + (byte)(keyAlgorithm & 0xff), fingerprint)); + } + + private static byte[] createData(byte signatureClass, byte keyAlgorithm, byte[] fingerprint) + { + byte[] data = new byte[2 + fingerprint.length]; + data[0] = signatureClass; + data[1] = keyAlgorithm; + System.arraycopy(fingerprint, 0, data, 2, fingerprint.length); + return data; + } + + public byte getSignatureClass() + { + return this.getData()[0]; + } + + public int getAlgorithm() + { + return this.getData()[1]; + } + + public byte[] getFingerprint() + { + byte[] data = this.getData(); + byte[] fingerprint = new byte[data.length - 2]; + System.arraycopy(data, 2, fingerprint, 0, fingerprint.length); + return fingerprint; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationKeyTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationKeyTags.java new file mode 100644 index 000000000..294fdd34e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationKeyTags.java @@ -0,0 +1,8 @@ +package org.spongycastle.bcpg.sig; + +public interface RevocationKeyTags +{ + public static final byte CLASS_DEFAULT = (byte)0x80; + public static final byte CLASS_SENSITIVE = (byte)0x40; + +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationReason.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationReason.java new file mode 100644 index 000000000..33cf451d3 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationReason.java @@ -0,0 +1,51 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.util.Strings; + +/** + * Represents revocation reason OpenPGP signature sub packet. + */ +public class RevocationReason extends SignatureSubpacket +{ + public RevocationReason(boolean isCritical, byte[] data) + { + super(SignatureSubpacketTags.REVOCATION_REASON, isCritical, data); + } + + public RevocationReason(boolean isCritical, byte reason, String description) + { + super(SignatureSubpacketTags.REVOCATION_REASON, isCritical, createData(reason, description)); + } + + private static byte[] createData(byte reason, String description) + { + byte[] descriptionBytes = Strings.toUTF8ByteArray(description); + byte[] data = new byte[1 + descriptionBytes.length]; + + data[0] = reason; + System.arraycopy(descriptionBytes, 0, data, 1, descriptionBytes.length); + + return data; + } + + public byte getRevocationReason() + { + return getData()[0]; + } + + public String getRevocationDescription() + { + byte[] data = getData(); + if (data.length == 1) + { + return ""; + } + + byte[] description = new byte[data.length - 1]; + System.arraycopy(data, 1, description, 0, description.length); + + return Strings.fromUTF8ByteArray(description); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationReasonTags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationReasonTags.java new file mode 100644 index 000000000..94233fb31 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/RevocationReasonTags.java @@ -0,0 +1,12 @@ +package org.spongycastle.bcpg.sig; + +public interface RevocationReasonTags +{ + public static final byte NO_REASON = 0; // No reason specified (key revocations or cert revocations) + public static final byte KEY_SUPERSEDED = 1; // Key is superseded (key revocations) + public static final byte KEY_COMPROMISED = 2; // Key material has been compromised (key revocations) + public static final byte KEY_RETIRED = 3; // Key is retired and no longer used (key revocations) + public static final byte USER_NO_LONGER_VALID = 32; // User ID information is no longer valid (cert revocations) + + // 100-110 - Private Use +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignatureCreationTime.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignatureCreationTime.java new file mode 100644 index 000000000..888cb9a9f --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignatureCreationTime.java @@ -0,0 +1,48 @@ +package org.spongycastle.bcpg.sig; + +import java.util.Date; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving signature creation time. + */ +public class SignatureCreationTime + extends SignatureSubpacket +{ + protected static byte[] timeToBytes( + Date date) + { + byte[] data = new byte[4]; + long t = date.getTime() / 1000; + + data[0] = (byte)(t >> 24); + data[1] = (byte)(t >> 16); + data[2] = (byte)(t >> 8); + data[3] = (byte)t; + + return data; + } + + public SignatureCreationTime( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.CREATION_TIME, critical, data); + } + + public SignatureCreationTime( + boolean critical, + Date date) + { + super(SignatureSubpacketTags.CREATION_TIME, critical, timeToBytes(date)); + } + + public Date getTime() + { + long time = ((long)(data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); + + return new Date(time * 1000); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignatureExpirationTime.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignatureExpirationTime.java new file mode 100644 index 000000000..bcf1444dc --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignatureExpirationTime.java @@ -0,0 +1,48 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving signature expiration time. + */ +public class SignatureExpirationTime + extends SignatureSubpacket +{ + protected static byte[] timeToBytes( + long t) + { + byte[] data = new byte[4]; + + data[0] = (byte)(t >> 24); + data[1] = (byte)(t >> 16); + data[2] = (byte)(t >> 8); + data[3] = (byte)t; + + return data; + } + + public SignatureExpirationTime( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.EXPIRE_TIME, critical, data); + } + + public SignatureExpirationTime( + boolean critical, + long seconds) + { + super(SignatureSubpacketTags.EXPIRE_TIME, critical, timeToBytes(seconds)); + } + + /** + * return time in seconds before signature expires after creation time. + */ + public long getTime() + { + long time = ((long)(data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); + + return time; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignerUserID.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignerUserID.java new file mode 100644 index 000000000..c0cb27865 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/SignerUserID.java @@ -0,0 +1,50 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving the User ID of the signer. + */ +public class SignerUserID + extends SignatureSubpacket +{ + private static byte[] userIDToBytes( + String id) + { + byte[] idData = new byte[id.length()]; + + for (int i = 0; i != id.length(); i++) + { + idData[i] = (byte)id.charAt(i); + } + + return idData; + } + + public SignerUserID( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.SIGNER_USER_ID, critical, data); + } + + public SignerUserID( + boolean critical, + String userID) + { + super(SignatureSubpacketTags.SIGNER_USER_ID, critical, userIDToBytes(userID)); + } + + public String getID() + { + char[] chars = new char[data.length]; + + for (int i = 0; i != chars.length; i++) + { + chars[i] = (char)(data[i] & 0xff); + } + + return new String(chars); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/TrustSignature.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/TrustSignature.java new file mode 100644 index 000000000..4555f2223 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/bcpg/sig/TrustSignature.java @@ -0,0 +1,48 @@ +package org.spongycastle.bcpg.sig; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; + +/** + * packet giving trust. + */ +public class TrustSignature + extends SignatureSubpacket +{ + private static byte[] intToByteArray( + int v1, + int v2) + { + byte[] data = new byte[2]; + + data[0] = (byte)v1; + data[1] = (byte)v2; + + return data; + } + + public TrustSignature( + boolean critical, + byte[] data) + { + super(SignatureSubpacketTags.TRUST_SIG, critical, data); + } + + public TrustSignature( + boolean critical, + int depth, + int trustAmount) + { + super(SignatureSubpacketTags.TRUST_SIG, critical, intToByteArray(depth, trustAmount)); + } + + public int getDepth() + { + return data[0] & 0xff; + } + + public int getTrustAmount() + { + return data[1] & 0xff; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPAlgorithmParameters.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPAlgorithmParameters.java new file mode 100644 index 000000000..a9628afbb --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPAlgorithmParameters.java @@ -0,0 +1,5 @@ +package org.spongycastle.openpgp; + +public interface PGPAlgorithmParameters +{ +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedData.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedData.java new file mode 100644 index 000000000..36e684416 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedData.java @@ -0,0 +1,143 @@ +package org.spongycastle.openpgp; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.CompressedDataPacket; +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.spongycastle.apache.bzip2.CBZip2InputStream; + +/** + * Compressed data objects. + */ +public class PGPCompressedData + implements CompressionAlgorithmTags +{ + CompressedDataPacket data; + + public PGPCompressedData( + BCPGInputStream pIn) + throws IOException + { + data = (CompressedDataPacket)pIn.readPacket(); + } + + /** + * Return the algorithm used for compression + * + * @return algorithm code + */ + public int getAlgorithm() + { + return data.getAlgorithm(); + } + + /** + * Return the raw input stream contained in the object. + * + * @return InputStream + */ + public InputStream getInputStream() + { + return data.getInputStream(); + } + + /** + * Return an uncompressed input stream which allows reading of the + * compressed data. + * + * @return InputStream + * @throws PGPException + */ + public InputStream getDataStream() + throws PGPException + { + if (this.getAlgorithm() == UNCOMPRESSED) + { + return this.getInputStream(); + } + if (this.getAlgorithm() == ZIP) + { + return new InflaterInputStream(this.getInputStream(), new Inflater(true)) + { + // If the "nowrap" inflater option is used the stream can + // apparently overread - we override fill() and provide + // an extra byte for the end of the input stream to get + // around this. + // + // Totally weird... + // + protected void fill() throws IOException + { + if (eof) + { + throw new EOFException("Unexpected end of ZIP input stream"); + } + + len = this.in.read(buf, 0, buf.length); + + if (len == -1) + { + buf[0] = 0; + len = 1; + eof = true; + } + + inf.setInput(buf, 0, len); + } + + private boolean eof = false; + }; + } + if (this.getAlgorithm() == ZLIB) + { + return new InflaterInputStream(this.getInputStream()) + { + // If the "nowrap" inflater option is used the stream can + // apparently overread - we override fill() and provide + // an extra byte for the end of the input stream to get + // around this. + // + // Totally weird... + // + protected void fill() throws IOException + { + if (eof) + { + throw new EOFException("Unexpected end of ZIP input stream"); + } + + len = this.in.read(buf, 0, buf.length); + + if (len == -1) + { + buf[0] = 0; + len = 1; + eof = true; + } + + inf.setInput(buf, 0, len); + } + + private boolean eof = false; + }; + } + if (this.getAlgorithm() == BZIP2) + { + try + { + return new CBZip2InputStream(this.getInputStream()); + } + catch (IOException e) + { + throw new PGPException("I/O problem with stream: " + e, e); + } + } + + throw new PGPException("can't recognise compression algorithm: " + this.getAlgorithm()); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedDataGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedDataGenerator.java new file mode 100644 index 000000000..c0e8c883d --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPCompressedDataGenerator.java @@ -0,0 +1,201 @@ +package org.spongycastle.openpgp; + +import org.spongycastle.apache.bzip2.CBZip2OutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.spongycastle.bcpg.PacketTags; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +/** + *class for producing compressed data packets. + */ +public class PGPCompressedDataGenerator + implements CompressionAlgorithmTags, StreamGenerator +{ + private int algorithm; + private int compression; + + private OutputStream dOut; + private BCPGOutputStream pkOut; + + public PGPCompressedDataGenerator( + int algorithm) + { + this(algorithm, Deflater.DEFAULT_COMPRESSION); + } + + public PGPCompressedDataGenerator( + int algorithm, + int compression) + { + switch (algorithm) + { + case CompressionAlgorithmTags.UNCOMPRESSED: + case CompressionAlgorithmTags.ZIP: + case CompressionAlgorithmTags.ZLIB: + case CompressionAlgorithmTags.BZIP2: + break; + default: + throw new IllegalArgumentException("unknown compression algorithm"); + } + + if (compression != Deflater.DEFAULT_COMPRESSION) + { + if ((compression < Deflater.NO_COMPRESSION) || (compression > Deflater.BEST_COMPRESSION)) + { + throw new IllegalArgumentException("unknown compression level: " + compression); + } + } + + this.algorithm = algorithm; + this.compression = compression; + } + + /** + * Return an OutputStream which will save the data being written to + * the compressed object. + * <p> + * The stream created can be closed off by either calling close() + * on the stream or close() on the generator. Closing the returned + * stream does not close off the OutputStream parameter out. + * + * @param out underlying OutputStream to be used. + * @return OutputStream + * @throws IOException, IllegalStateException + */ + public OutputStream open( + OutputStream out) + throws IOException + { + if (dOut != null) + { + throw new IllegalStateException("generator already in open state"); + } + + this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA); + + doOpen(); + + return new WrappedGeneratorStream(dOut, this); + } + + /** + * Return an OutputStream which will compress the data as it is written + * to it. The stream will be written out in chunks according to the size of the + * passed in buffer. + * <p> + * The stream created can be closed off by either calling close() + * on the stream or close() on the generator. Closing the returned + * stream does not close off the OutputStream parameter out. + * <p> + * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2 + * bytes worth of the buffer will be used. + * </p> + * <p> + * <b>Note</b>: using this may break compatibility with RFC 1991 compliant tools. Only recent OpenPGP + * implementations are capable of accepting these streams. + * </p> + * + * @param out underlying OutputStream to be used. + * @param buffer the buffer to use. + * @return OutputStream + * @throws IOException + * @throws PGPException + */ + public OutputStream open( + OutputStream out, + byte[] buffer) + throws IOException, PGPException + { + if (dOut != null) + { + throw new IllegalStateException("generator already in open state"); + } + + this.pkOut = new BCPGOutputStream(out, PacketTags.COMPRESSED_DATA, buffer); + + doOpen(); + + return new WrappedGeneratorStream(dOut, this); + } + + private void doOpen() throws IOException + { + pkOut.write(algorithm); + + switch (algorithm) + { + case CompressionAlgorithmTags.UNCOMPRESSED: + dOut = pkOut; + break; + case CompressionAlgorithmTags.ZIP: + dOut = new SafeDeflaterOutputStream(pkOut, compression, true); + break; + case CompressionAlgorithmTags.ZLIB: + dOut = new SafeDeflaterOutputStream(pkOut, compression, false); + break; + case CompressionAlgorithmTags.BZIP2: + dOut = new SafeCBZip2OutputStream(pkOut); + break; + default: + // Constructor should guard against this possibility + throw new IllegalStateException(); + } + } + + /** + * Close the compressed object - this is equivalent to calling close on the stream + * returned by the open() method. + * + * @throws IOException + */ + public void close() + throws IOException + { + if (dOut != null) + { + if (dOut != pkOut) + { + dOut.close(); + dOut.flush(); + } + + dOut = null; + + pkOut.finish(); + pkOut.flush(); + pkOut = null; + } + } + + private static class SafeCBZip2OutputStream extends CBZip2OutputStream + { + public SafeCBZip2OutputStream(OutputStream output) throws IOException + { + super(output); + } + + public void close() throws IOException + { + finish(); + } + } + + private class SafeDeflaterOutputStream extends DeflaterOutputStream + { + public SafeDeflaterOutputStream(OutputStream output, int compression, boolean nowrap) + { + super(output, new Deflater(compression, nowrap)); + } + + public void close() throws IOException + { + finish(); + def.end(); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPDataValidationException.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPDataValidationException.java new file mode 100644 index 000000000..1b6fb10b7 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPDataValidationException.java @@ -0,0 +1,17 @@ +package org.spongycastle.openpgp; + +/** + * Thrown if the iv at the start of a data stream indicates the wrong key + * is being used. + */ +public class PGPDataValidationException + extends PGPException +{ + /** + * @param message + */ + public PGPDataValidationException(String message) + { + super(message); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedData.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedData.java new file mode 100644 index 000000000..7ff398101 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedData.java @@ -0,0 +1,147 @@ +package org.spongycastle.openpgp; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.spongycastle.bcpg.InputStreamPacket; +import org.spongycastle.bcpg.SymmetricEncIntegrityPacket; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.util.Arrays; + +public abstract class PGPEncryptedData + implements SymmetricKeyAlgorithmTags +{ + protected class TruncatedStream extends InputStream + { + int[] lookAhead = new int[22]; + int bufPtr; + InputStream in; + + TruncatedStream( + InputStream in) + throws IOException + { + for (int i = 0; i != lookAhead.length; i++) + { + if ((lookAhead[i] = in.read()) < 0) + { + throw new EOFException(); + } + } + + bufPtr = 0; + this.in = in; + } + + public int read() + throws IOException + { + int ch = in.read(); + + if (ch >= 0) + { + int c = lookAhead[bufPtr]; + + lookAhead[bufPtr] = ch; + bufPtr = (bufPtr + 1) % lookAhead.length; + + return c; + } + + return -1; + } + + int[] getLookAhead() + { + int[] tmp = new int[lookAhead.length]; + int count = 0; + + for (int i = bufPtr; i != lookAhead.length; i++) + { + tmp[count++] = lookAhead[i]; + } + for (int i = 0; i != bufPtr; i++) + { + tmp[count++] = lookAhead[i]; + } + + return tmp; + } + } + + InputStreamPacket encData; + InputStream encStream; + TruncatedStream truncStream; + PGPDigestCalculator integrityCalculator; + + PGPEncryptedData( + InputStreamPacket encData) + { + this.encData = encData; + } + + /** + * Return the raw input stream for the data stream. + * + * @return InputStream + */ + public InputStream getInputStream() + { + return encData.getInputStream(); + } + + /** + * Return true if the message is integrity protected. + * @return true if there is a modification detection code package associated with this stream + */ + public boolean isIntegrityProtected() + { + return (encData instanceof SymmetricEncIntegrityPacket); + } + + /** + * Note: This can only be called after the message has been read. + * + * @return true if the message verifies, false otherwise. + * @throws PGPException if the message is not integrity protected. + */ + public boolean verify() + throws PGPException, IOException + { + if (!this.isIntegrityProtected()) + { + throw new PGPException("data not integrity protected."); + } + + // + // make sure we are at the end. + // + while (encStream.read() >= 0) + { + // do nothing + } + + // + // process the MDC packet + // + int[] lookAhead = truncStream.getLookAhead(); + + OutputStream dOut = integrityCalculator.getOutputStream(); + + dOut.write((byte)lookAhead[0]); + dOut.write((byte)lookAhead[1]); + + byte[] digest = integrityCalculator.getDigest(); + byte[] streamDigest = new byte[digest.length]; + + for (int i = 0; i != streamDigest.length; i++) + { + streamDigest[i] = (byte)lookAhead[i + 2]; + } + + return Arrays.constantTimeAreEqual(digest, streamDigest); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataGenerator.java new file mode 100644 index 000000000..9f816e392 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataGenerator.java @@ -0,0 +1,537 @@ +package org.spongycastle.openpgp; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PacketTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.PGPDataEncryptor; +import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.spongycastle.util.io.TeeOutputStream; + +/** + * Generator for encrypted objects. + */ +public class PGPEncryptedDataGenerator + implements SymmetricKeyAlgorithmTags, StreamGenerator +{ + /** + * Specifier for SHA-1 S2K PBE generator. + */ + public static final int S2K_SHA1 = HashAlgorithmTags.SHA1; + + /** + * Specifier for SHA-224 S2K PBE generator. + */ + public static final int S2K_SHA224 = HashAlgorithmTags.SHA224; + + /** + * Specifier for SHA-256 S2K PBE generator. + */ + public static final int S2K_SHA256 = HashAlgorithmTags.SHA256; + + /** + * Specifier for SHA-384 S2K PBE generator. + */ + public static final int S2K_SHA384 = HashAlgorithmTags.SHA384; + + /** + * Specifier for SHA-512 S2K PBE generator. + */ + public static final int S2K_SHA512 = HashAlgorithmTags.SHA512; + + private BCPGOutputStream pOut; + private OutputStream cOut; + private boolean oldFormat = false; + private PGPDigestCalculator digestCalc; + private OutputStream genOut; + private PGPDataEncryptorBuilder dataEncryptorBuilder; + + private List methods = new ArrayList(); + private int defAlgorithm; + private SecureRandom rand; + + private static Provider defProvider; + + /** + * Base constructor. + * + * @param encAlgorithm the symmetric algorithm to use. + * @param rand source of randomness + * @param provider the provider name to use for encryption algorithms. + * @deprecated use constructor that takes a PGPDataEncryptor + */ + public PGPEncryptedDataGenerator( + int encAlgorithm, + SecureRandom rand, + String provider) + { + this(new JcePGPDataEncryptorBuilder(encAlgorithm).setSecureRandom(rand).setProvider(provider)); + } + + /** + * Base constructor. + * + * @param encAlgorithm the symmetric algorithm to use. + * @param rand source of randomness + * @param provider the provider to use for encryption algorithms. + * @deprecated use constructor that takes a PGPDataEncryptorBuilder + */ + public PGPEncryptedDataGenerator( + int encAlgorithm, + SecureRandom rand, + Provider provider) + { + this(new JcePGPDataEncryptorBuilder(encAlgorithm).setSecureRandom(rand).setProvider(provider)); + } + + /** + * Creates a cipher stream which will have an integrity packet + * associated with it. + * + * @param encAlgorithm + * @param withIntegrityPacket + * @param rand + * @param provider + * @deprecated use constructor that takes a PGPDataEncryptorBuilder + */ + public PGPEncryptedDataGenerator( + int encAlgorithm, + boolean withIntegrityPacket, + SecureRandom rand, + String provider) + { + this(new JcePGPDataEncryptorBuilder(encAlgorithm).setWithIntegrityPacket(withIntegrityPacket).setSecureRandom(rand).setProvider(provider)); + } + + /** + * Creates a cipher stream which will have an integrity packet + * associated with it. + * + * @param encAlgorithm + * @param withIntegrityPacket + * @param rand + * @param provider + * @deprecated use constructor that takes a PGPDataEncryptorBuilder + */ + public PGPEncryptedDataGenerator( + int encAlgorithm, + boolean withIntegrityPacket, + SecureRandom rand, + Provider provider) + { + this(new JcePGPDataEncryptorBuilder(encAlgorithm).setWithIntegrityPacket(withIntegrityPacket).setSecureRandom(rand).setProvider(provider)); + } + + /** + * Base constructor. + * + * @param encAlgorithm the symmetric algorithm to use. + * @param rand source of randomness + * @param oldFormat PGP 2.6.x compatibility required. + * @param provider the provider to use for encryption algorithms. + * @deprecated use constructor that takes a PGPDataEncryptorBuilder + */ + public PGPEncryptedDataGenerator( + int encAlgorithm, + SecureRandom rand, + boolean oldFormat, + String provider) + { + this(new JcePGPDataEncryptorBuilder(encAlgorithm).setSecureRandom(rand).setProvider(provider), oldFormat); + } + + /** + * Base constructor. + * + * @param encAlgorithm the symmetric algorithm to use. + * @param rand source of randomness + * @param oldFormat PGP 2.6.x compatibility required. + * @param provider the provider to use for encryption algorithms. + * @deprecated use constructor that takes a PGPDataEncryptorBuilder + */ + public PGPEncryptedDataGenerator( + int encAlgorithm, + SecureRandom rand, + boolean oldFormat, + Provider provider) + { + this(new JcePGPDataEncryptorBuilder(encAlgorithm).setSecureRandom(rand).setProvider(provider), oldFormat); + } + + /** + * Base constructor. + * + * @param encryptorBuilder builder to create actual data encryptor. + */ + public PGPEncryptedDataGenerator(PGPDataEncryptorBuilder encryptorBuilder) + { + this(encryptorBuilder, false); + } + + /** + * Base constructor with the option to turn on formatting for PGP 2.6.x compatibility. + * + * @param encryptorBuilder builder to create actual data encryptor. + * @param oldFormat PGP 2.6.x compatibility required. + */ + public PGPEncryptedDataGenerator(PGPDataEncryptorBuilder encryptorBuilder, boolean oldFormat) + { + this.dataEncryptorBuilder = encryptorBuilder; + this.oldFormat = oldFormat; + + this.defAlgorithm = dataEncryptorBuilder.getAlgorithm(); + this.rand = dataEncryptorBuilder.getSecureRandom(); + } + + /** + * Add a PBE encryption method to the encrypted object using the default algorithm (S2K_SHA1). + * + * @param passPhrase + * @throws NoSuchProviderException + * @throws PGPException + * @deprecated use addMethod that takes PGPKeyEncryptionMethodGenerator + */ + public void addMethod( + char[] passPhrase) + throws NoSuchProviderException, PGPException + { + addMethod(passPhrase, HashAlgorithmTags.SHA1); + } + + /** + * Add a PBE encryption method to the encrypted object. + * + * @param passPhrase passphrase to use to generate key. + * @param s2kDigest digest algorithm to use for S2K calculation + * @throws NoSuchProviderException + * @throws PGPException + * @deprecated use addMethod that takes PGPKeyEncryptionMethodGenerator + */ + public void addMethod( + char[] passPhrase, + int s2kDigest) + throws NoSuchProviderException, PGPException + { + if (defProvider == null) + { + defProvider = new BouncyCastleProvider(); + } + + addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase, new JcaPGPDigestCalculatorProviderBuilder().setProvider(defProvider).build().get(s2kDigest)).setProvider(defProvider).setSecureRandom(rand)); + } + + /** + * Add a public key encrypted session key to the encrypted object. + * + * @param key + * @throws NoSuchProviderException + * @throws PGPException + * @deprecated use addMethod that takes PGPKeyEncryptionMethodGenerator + */ + public void addMethod( + PGPPublicKey key) + throws NoSuchProviderException, PGPException + { + if (!key.isEncryptionKey()) + { + throw new IllegalArgumentException("passed in key not an encryption key!"); + } + + if (defProvider == null) + { + defProvider = new BouncyCastleProvider(); + } + + addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(key).setProvider(defProvider).setSecureRandom(rand)); + } + + /** + * Added a key encryption method to be used to encrypt the session data associated + * with this encrypted data. + * + * @param method key encryption method to use. + */ + public void addMethod(PGPKeyEncryptionMethodGenerator method) + { + methods.add(method); + } + + private void addCheckSum( + byte[] sessionInfo) + { + int check = 0; + + for (int i = 1; i != sessionInfo.length - 2; i++) + { + check += sessionInfo[i] & 0xff; + } + + sessionInfo[sessionInfo.length - 2] = (byte)(check >> 8); + sessionInfo[sessionInfo.length - 1] = (byte)(check); + } + + private byte[] createSessionInfo( + int algorithm, + byte[] keyBytes) + { + byte[] sessionInfo = new byte[keyBytes.length + 3]; + sessionInfo[0] = (byte) algorithm; + System.arraycopy(keyBytes, 0, sessionInfo, 1, keyBytes.length); + addCheckSum(sessionInfo); + return sessionInfo; + } + + /** + * If buffer is non null stream assumed to be partial, otherwise the + * length will be used to output a fixed length packet. + * <p> + * The stream created can be closed off by either calling close() + * on the stream or close() on the generator. Closing the returned + * stream does not close off the OutputStream parameter out. + * + * @param out + * @param length + * @param buffer + * @return + * @throws java.io.IOException + * @throws PGPException + * @throws IllegalStateException + */ + private OutputStream open( + OutputStream out, + long length, + byte[] buffer) + throws IOException, PGPException, IllegalStateException + { + if (cOut != null) + { + throw new IllegalStateException("generator already in open state"); + } + + if (methods.size() == 0) + { + throw new IllegalStateException("no encryption methods specified"); + } + + byte[] key = null; + + pOut = new BCPGOutputStream(out); + + defAlgorithm = dataEncryptorBuilder.getAlgorithm(); + rand = dataEncryptorBuilder.getSecureRandom(); + + if (methods.size() == 1) + { + + if (methods.get(0) instanceof PBEKeyEncryptionMethodGenerator) + { + PBEKeyEncryptionMethodGenerator m = (PBEKeyEncryptionMethodGenerator)methods.get(0); + + key = m.getKey(dataEncryptorBuilder.getAlgorithm()); + + pOut.writePacket(((PGPKeyEncryptionMethodGenerator)methods.get(0)).generate(defAlgorithm, null)); + } + else + { + key = PGPUtil.makeRandomKey(defAlgorithm, rand); + byte[] sessionInfo = createSessionInfo(defAlgorithm, key); + PGPKeyEncryptionMethodGenerator m = (PGPKeyEncryptionMethodGenerator)methods.get(0); + + pOut.writePacket(m.generate(defAlgorithm, sessionInfo)); + } + } + else // multiple methods + { + key = PGPUtil.makeRandomKey(defAlgorithm, rand); + byte[] sessionInfo = createSessionInfo(defAlgorithm, key); + + for (int i = 0; i != methods.size(); i++) + { + PGPKeyEncryptionMethodGenerator m = (PGPKeyEncryptionMethodGenerator)methods.get(i); + + pOut.writePacket(m.generate(defAlgorithm, sessionInfo)); + } + } + + try + { + PGPDataEncryptor dataEncryptor = dataEncryptorBuilder.build(key); + + digestCalc = dataEncryptor.getIntegrityCalculator(); + + if (buffer == null) + { + // + // we have to add block size + 2 for the generated IV and + 1 + 22 if integrity protected + // + if (digestCalc != null) + { + pOut = new ClosableBCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, length + dataEncryptor.getBlockSize() + 2 + 1 + 22); + + pOut.write(1); // version number + } + else + { + pOut = new ClosableBCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, length + dataEncryptor.getBlockSize() + 2, oldFormat); + } + } + else + { + if (digestCalc != null) + { + pOut = new ClosableBCPGOutputStream(out, PacketTags.SYM_ENC_INTEGRITY_PRO, buffer); + pOut.write(1); // version number + } + else + { + pOut = new ClosableBCPGOutputStream(out, PacketTags.SYMMETRIC_KEY_ENC, buffer); + } + } + + genOut = cOut = dataEncryptor.getOutputStream(pOut); + + if (digestCalc != null) + { + genOut = new TeeOutputStream(digestCalc.getOutputStream(), cOut); + } + + byte[] inLineIv = new byte[dataEncryptor.getBlockSize() + 2]; + rand.nextBytes(inLineIv); + inLineIv[inLineIv.length - 1] = inLineIv[inLineIv.length - 3]; + inLineIv[inLineIv.length - 2] = inLineIv[inLineIv.length - 4]; + + genOut.write(inLineIv); + + return new WrappedGeneratorStream(genOut, this); + } + catch (Exception e) + { + throw new PGPException("Exception creating cipher", e); + } + } + + /** + * Return an outputstream which will encrypt the data as it is written + * to it. + * <p> + * The stream created can be closed off by either calling close() + * on the stream or close() on the generator. Closing the returned + * stream does not close off the OutputStream parameter out. + * + * @param out + * @param length + * @return OutputStream + * @throws IOException + * @throws PGPException + */ + public OutputStream open( + OutputStream out, + long length) + throws IOException, PGPException + { + return this.open(out, length, null); + } + + /** + * Return an outputstream which will encrypt the data as it is written + * to it. The stream will be written out in chunks according to the size of the + * passed in buffer. + * <p> + * The stream created can be closed off by either calling close() + * on the stream or close() on the generator. Closing the returned + * stream does not close off the OutputStream parameter out. + * <p> + * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2 + * bytes worth of the buffer will be used. + * + * @param out + * @param buffer the buffer to use. + * @return OutputStream + * @throws IOException + * @throws PGPException + */ + public OutputStream open( + OutputStream out, + byte[] buffer) + throws IOException, PGPException + { + return this.open(out, 0, buffer); + } + + /** + * Close off the encrypted object - this is equivalent to calling close on the stream + * returned by the open() method. + * <p> + * <b>Note</b>: This does not close the underlying output stream, only the stream on top of it created by the open() method. + * @throws java.io.IOException + */ + public void close() + throws IOException + { + if (cOut != null) + { + if (digestCalc != null) + { + // + // hand code a mod detection packet + // + BCPGOutputStream bOut = new BCPGOutputStream(genOut, PacketTags.MOD_DETECTION_CODE, 20); + + bOut.flush(); + + byte[] dig = digestCalc.getDigest(); + + cOut.write(dig); + } + + cOut.close(); + + cOut = null; + pOut = null; + } + } + + private class ClosableBCPGOutputStream + extends BCPGOutputStream + { + public ClosableBCPGOutputStream(OutputStream out, int symmetricKeyEnc, byte[] buffer) + throws IOException + { + super(out, symmetricKeyEnc, buffer); + } + + public ClosableBCPGOutputStream(OutputStream out, int symmetricKeyEnc, long length, boolean oldFormat) + throws IOException + { + super(out, symmetricKeyEnc, length, oldFormat); + } + + public ClosableBCPGOutputStream(OutputStream out, int symEncIntegrityPro, long length) + throws IOException + { + super(out, symEncIntegrityPro, length); + } + + public void close() + throws IOException + { + this.finish(); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataList.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataList.java new file mode 100644 index 000000000..8fec479e6 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPEncryptedDataList.java @@ -0,0 +1,75 @@ +package org.spongycastle.openpgp; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.InputStreamPacket; +import org.spongycastle.bcpg.PacketTags; +import org.spongycastle.bcpg.PublicKeyEncSessionPacket; +import org.spongycastle.bcpg.SymmetricKeyEncSessionPacket; + +/** + * A holder for a list of PGP encryption method packets. + */ +public class PGPEncryptedDataList +{ + List list = new ArrayList(); + InputStreamPacket data; + + public PGPEncryptedDataList( + BCPGInputStream pIn) + throws IOException + { + while (pIn.nextPacketTag() == PacketTags.PUBLIC_KEY_ENC_SESSION + || pIn.nextPacketTag() == PacketTags.SYMMETRIC_KEY_ENC_SESSION) + { + list.add(pIn.readPacket()); + } + + data = (InputStreamPacket)pIn.readPacket(); + + for (int i = 0; i != list.size(); i++) + { + if (list.get(i) instanceof SymmetricKeyEncSessionPacket) + { + list.set(i, new PGPPBEEncryptedData((SymmetricKeyEncSessionPacket)list.get(i), data)); + } + else + { + list.set(i, new PGPPublicKeyEncryptedData((PublicKeyEncSessionPacket)list.get(i), data)); + } + } + } + + public Object get( + int index) + { + return list.get(index); + } + + public int size() + { + return list.size(); + } + + public boolean isEmpty() + { + return list.isEmpty(); + } + + /** + * @deprecated misspelt - use getEncryptedDataObjects() + */ + public Iterator getEncyptedDataObjects() + { + return list.iterator(); + } + + public Iterator getEncryptedDataObjects() + { + return list.iterator(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPException.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPException.java new file mode 100644 index 000000000..220672ac6 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPException.java @@ -0,0 +1,35 @@ +package org.spongycastle.openpgp; + +/** + * generic exception class for PGP encoding/decoding problems + */ +public class PGPException + extends Exception +{ + Exception underlying; + + public PGPException( + String message) + { + super(message); + } + + public PGPException( + String message, + Exception underlying) + { + super(message); + this.underlying = underlying; + } + + public Exception getUnderlyingException() + { + return underlying; + } + + + public Throwable getCause() + { + return underlying; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKdfParameters.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKdfParameters.java new file mode 100644 index 000000000..fc37af74b --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKdfParameters.java @@ -0,0 +1,24 @@ +package org.spongycastle.openpgp; + +public class PGPKdfParameters + implements PGPAlgorithmParameters +{ + private final int hashAlgorithm; + private final int symmetricWrapAlgorithm; + + public PGPKdfParameters(int hashAlgorithm, int symmetricWrapAlgorithm) + { + this.hashAlgorithm = hashAlgorithm; + this.symmetricWrapAlgorithm = symmetricWrapAlgorithm; + } + + public int getSymmetricWrapAlgorithm() + { + return symmetricWrapAlgorithm; + } + + public int getHashAlgorithm() + { + return hashAlgorithm; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyFlags.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyFlags.java new file mode 100644 index 000000000..2c1691268 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyFlags.java @@ -0,0 +1,19 @@ +package org.spongycastle.openpgp; + +/** + * key flag values for the KeyFlags subpacket. + */ +public interface PGPKeyFlags +{ + public static final int CAN_CERTIFY = 0x01; // This key may be used to certify other keys. + + public static final int CAN_SIGN = 0x02; // This key may be used to sign data. + + public static final int CAN_ENCRYPT_COMMS = 0x04; // This key may be used to encrypt communications. + + public static final int CAN_ENCRYPT_STORAGE = 0x08; // This key may be used to encrypt storage. + + public static final int MAYBE_SPLIT = 0x10; // The private component of this key may have been split by a secret-sharing mechanism. + + public static final int MAYBE_SHARED = 0x80; // The private component of this key may be in the possession of more than one person. +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyPair.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyPair.java new file mode 100644 index 000000000..0b430c71d --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyPair.java @@ -0,0 +1,148 @@ +package org.spongycastle.openpgp; + +import java.security.KeyPair; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.util.Date; + +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.DSASecretBCPGKey; +import org.spongycastle.bcpg.ElGamalSecretBCPGKey; +import org.spongycastle.bcpg.RSASecretBCPGKey; +import org.spongycastle.jce.interfaces.ElGamalPrivateKey; + + +/** + * General class to handle JCA key pairs and convert them into OpenPGP ones. + * <p> + * A word for the unwary, the KeyID for a OpenPGP public key is calculated from + * a hash that includes the time of creation, if you pass a different date to the + * constructor below with the same public private key pair the KeyID will not be the + * same as for previous generations of the key, so ideally you only want to do + * this once. + */ +public class PGPKeyPair +{ + protected PGPPublicKey pub; + protected PGPPrivateKey priv; + + /** + * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate. + */ + public PGPKeyPair( + int algorithm, + KeyPair keyPair, + Date time, + String provider) + throws PGPException, NoSuchProviderException + { + this(algorithm, keyPair.getPublic(), keyPair.getPrivate(), time, provider); + } + + /** + * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate. + */ + public PGPKeyPair( + int algorithm, + KeyPair keyPair, + Date time) + throws PGPException + { + this(algorithm, keyPair.getPublic(), keyPair.getPrivate(), time); + } + + /** + * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate. + */ + public PGPKeyPair( + int algorithm, + PublicKey pubKey, + PrivateKey privKey, + Date time, + String provider) + throws PGPException, NoSuchProviderException + { + this(algorithm, pubKey, privKey, time); + } + + /** + * @deprecated use BcPGPKeyPair or JcaPGPKeyPair as appropriate. + */ + public PGPKeyPair( + int algorithm, + PublicKey pubKey, + PrivateKey privKey, + Date time) + throws PGPException + { + this.pub = new PGPPublicKey(algorithm, pubKey, time); + + BCPGKey privPk; + + switch (pub.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_SIGN: + case PGPPublicKey.RSA_GENERAL: + RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privKey; + + privPk = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ()); + break; + case PGPPublicKey.DSA: + DSAPrivateKey dsK = (DSAPrivateKey)privKey; + + privPk = new DSASecretBCPGKey(dsK.getX()); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPrivateKey esK = (ElGamalPrivateKey)privKey; + + privPk = new ElGamalSecretBCPGKey(esK.getX()); + break; + default: + throw new PGPException("unknown key class"); + } + this.priv = new PGPPrivateKey(pub.getKeyID(), pub.getPublicKeyPacket(), privPk); + } + + /** + * Create a key pair from a PGPPrivateKey and a PGPPublicKey. + * + * @param pub the public key + * @param priv the private key + */ + public PGPKeyPair( + PGPPublicKey pub, + PGPPrivateKey priv) + { + this.pub = pub; + this.priv = priv; + } + + protected PGPKeyPair() + { + } + + /** + * Return the keyID associated with this key pair. + * + * @return keyID + */ + public long getKeyID() + { + return pub.getKeyID(); + } + + public PGPPublicKey getPublicKey() + { + return pub; + } + + public PGPPrivateKey getPrivateKey() + { + return priv; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRing.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRing.java new file mode 100644 index 000000000..50afce961 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRing.java @@ -0,0 +1,125 @@ +package org.spongycastle.openpgp; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.Packet; +import org.spongycastle.bcpg.PacketTags; +import org.spongycastle.bcpg.SignaturePacket; +import org.spongycastle.bcpg.TrustPacket; +import org.spongycastle.bcpg.UserAttributePacket; +import org.spongycastle.bcpg.UserIDPacket; + +public abstract class PGPKeyRing +{ + PGPKeyRing() + { + } + + static BCPGInputStream wrap(InputStream in) + { + if (in instanceof BCPGInputStream) + { + return (BCPGInputStream)in; + } + + return new BCPGInputStream(in); + } + + static TrustPacket readOptionalTrustPacket( + BCPGInputStream pIn) + throws IOException + { + return (pIn.nextPacketTag() == PacketTags.TRUST) + ? (TrustPacket) pIn.readPacket() + : null; + } + + static List readSignaturesAndTrust( + BCPGInputStream pIn) + throws IOException + { + try + { + List sigList = new ArrayList(); + + while (pIn.nextPacketTag() == PacketTags.SIGNATURE) + { + SignaturePacket signaturePacket = (SignaturePacket)pIn.readPacket(); + TrustPacket trustPacket = readOptionalTrustPacket(pIn); + + sigList.add(new PGPSignature(signaturePacket, trustPacket)); + } + + return sigList; + } + catch (PGPException e) + { + throw new IOException("can't create signature object: " + e.getMessage() + + ", cause: " + e.getUnderlyingException().toString()); + } + } + + static void readUserIDs( + BCPGInputStream pIn, + List ids, + List idTrusts, + List idSigs) + throws IOException + { + while (pIn.nextPacketTag() == PacketTags.USER_ID + || pIn.nextPacketTag() == PacketTags.USER_ATTRIBUTE) + { + Packet obj = pIn.readPacket(); + if (obj instanceof UserIDPacket) + { + UserIDPacket id = (UserIDPacket)obj; + ids.add(id.getID()); + } + else + { + UserAttributePacket user = (UserAttributePacket)obj; + ids.add(new PGPUserAttributeSubpacketVector(user.getSubpackets())); + } + + idTrusts.add(readOptionalTrustPacket(pIn)); + idSigs.add(readSignaturesAndTrust(pIn)); + } + } + + /** + * Return the first public key in the ring. In the case of a {@link PGPSecretKeyRing} + * this is also the public key of the master key pair. + * + * @return PGPPublicKey + */ + public abstract PGPPublicKey getPublicKey(); + + /** + * Return an iterator containing all the public keys. + * + * @return Iterator + */ + public abstract Iterator getPublicKeys(); + + /** + * Return the public key referred to by the passed in keyID if it + * is present. + * + * @param keyID + * @return PGPPublicKey + */ + public abstract PGPPublicKey getPublicKey(long keyID); + + public abstract void encode(OutputStream outStream) + throws IOException; + + public abstract byte[] getEncoded() + throws IOException; + +}
\ No newline at end of file diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRingGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRingGenerator.java new file mode 100644 index 000000000..dc3fae711 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyRingGenerator.java @@ -0,0 +1,272 @@ +package org.spongycastle.openpgp; + +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicSubkeyPacket; +import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; + +/** + * Generator for a PGP master and subkey ring. This class will generate + * both the secret and public key rings + */ +public class PGPKeyRingGenerator +{ + List keys = new ArrayList(); + + private PBESecretKeyEncryptor keyEncryptor; + private PGPDigestCalculator checksumCalculator; + private PGPKeyPair masterKey; + private PGPSignatureSubpacketVector hashedPcks; + private PGPSignatureSubpacketVector unhashedPcks; + private PGPContentSignerBuilder keySignerBuilder; + + /** + * Create a new key ring generator using old style checksumming. It is recommended to use + * SHA1 checksumming where possible. + * + * @param certificationLevel the certification level for keys on this ring. + * @param masterKey the master key pair. + * @param id the id to be associated with the ring. + * @param encAlgorithm the algorithm to be used to protect secret keys. + * @param passPhrase the passPhrase to be used to protect secret keys. + * @param hashedPcks packets to be included in the certification hash. + * @param unhashedPcks packets to be attached unhashed to the certification. + * @param rand input secured random + * @param provider the provider to use for encryption. + * + * @throws PGPException + * @throws NoSuchProviderException + * @deprecated use method taking PBESecretKeyDecryptor + */ + public PGPKeyRingGenerator( + int certificationLevel, + PGPKeyPair masterKey, + String id, + int encAlgorithm, + char[] passPhrase, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + SecureRandom rand, + String provider) + throws PGPException, NoSuchProviderException + { + this(certificationLevel, masterKey, id, encAlgorithm, passPhrase, false, hashedPcks, unhashedPcks, rand, provider); + } + + /** + * Create a new key ring generator. + * + * @param certificationLevel the certification level for keys on this ring. + * @param masterKey the master key pair. + * @param id the id to be associated with the ring. + * @param encAlgorithm the algorithm to be used to protect secret keys. + * @param passPhrase the passPhrase to be used to protect secret keys. + * @param useSHA1 checksum the secret keys with SHA1 rather than the older 16 bit checksum. + * @param hashedPcks packets to be included in the certification hash. + * @param unhashedPcks packets to be attached unhashed to the certification. + * @param rand input secured random + * @param provider the provider to use for encryption. + * + * @throws PGPException + * @throws NoSuchProviderException + * @deprecated use method taking PBESecretKeyDecryptor + */ + public PGPKeyRingGenerator( + int certificationLevel, + PGPKeyPair masterKey, + String id, + int encAlgorithm, + char[] passPhrase, + boolean useSHA1, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + SecureRandom rand, + String provider) + throws PGPException, NoSuchProviderException + { + this(certificationLevel, masterKey, id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, PGPUtil.getProvider(provider)); + } + + /** + * Create a new key ring generator. + * + * @param certificationLevel the certification level for keys on this ring. + * @param masterKey the master key pair. + * @param id the id to be associated with the ring. + * @param encAlgorithm the algorithm to be used to protect secret keys. + * @param passPhrase the passPhrase to be used to protect secret keys. + * @param useSHA1 checksum the secret keys with SHA1 rather than the older 16 bit checksum. + * @param hashedPcks packets to be included in the certification hash. + * @param unhashedPcks packets to be attached unhashed to the certification. + * @param rand input secured random + * @param provider the provider to use for encryption. + * + * @throws PGPException + * @throws NoSuchProviderException + * @deprecated use method taking PBESecretKeyEncryptor + */ + public PGPKeyRingGenerator( + int certificationLevel, + PGPKeyPair masterKey, + String id, + int encAlgorithm, + char[] passPhrase, + boolean useSHA1, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + SecureRandom rand, + Provider provider) + throws PGPException, NoSuchProviderException + { + this.masterKey = masterKey; + this.hashedPcks = hashedPcks; + this.unhashedPcks = unhashedPcks; + this.keyEncryptor = new JcePBESecretKeyEncryptorBuilder(encAlgorithm).setProvider(provider).setSecureRandom(rand).build(passPhrase); + this.checksumCalculator = convertSHA1Flag(useSHA1); + this.keySignerBuilder = new JcaPGPContentSignerBuilder(masterKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1); + + keys.add(new PGPSecretKey(certificationLevel, masterKey, id, checksumCalculator, hashedPcks, unhashedPcks, keySignerBuilder, keyEncryptor)); + } + + /** + * Create a new key ring generator. + * + * @param certificationLevel + * @param masterKey + * @param id + * @param checksumCalculator + * @param hashedPcks + * @param unhashedPcks + * @param keySignerBuilder + * @param keyEncryptor + * @throws PGPException + */ + public PGPKeyRingGenerator( + int certificationLevel, + PGPKeyPair masterKey, + String id, + PGPDigestCalculator checksumCalculator, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + PGPContentSignerBuilder keySignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + this.masterKey = masterKey; + this.keyEncryptor = keyEncryptor; + this.checksumCalculator = checksumCalculator; + this.keySignerBuilder = keySignerBuilder; + this.hashedPcks = hashedPcks; + this.unhashedPcks = unhashedPcks; + + keys.add(new PGPSecretKey(certificationLevel, masterKey, id, checksumCalculator, hashedPcks, unhashedPcks, keySignerBuilder, keyEncryptor)); + } + + /** + * Add a sub key to the key ring to be generated with default certification and inheriting + * the hashed/unhashed packets of the master key. + * + * @param keyPair + * @throws PGPException + */ + public void addSubKey( + PGPKeyPair keyPair) + throws PGPException + { + addSubKey(keyPair, hashedPcks, unhashedPcks); + } + + /** + * Add a subkey with specific hashed and unhashed packets associated with it and default + * certification. + * + * @param keyPair public/private key pair. + * @param hashedPcks hashed packet values to be included in certification. + * @param unhashedPcks unhashed packets values to be included in certification. + * @throws PGPException + */ + public void addSubKey( + PGPKeyPair keyPair, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks) + throws PGPException + { + try + { + // + // generate the certification + // + PGPSignatureGenerator sGen = new PGPSignatureGenerator(keySignerBuilder); + + sGen.init(PGPSignature.SUBKEY_BINDING, masterKey.getPrivateKey()); + + sGen.setHashedSubpackets(hashedPcks); + sGen.setUnhashedSubpackets(unhashedPcks); + + List subSigs = new ArrayList(); + + subSigs.add(sGen.generateCertification(masterKey.getPublicKey(), keyPair.getPublicKey())); + + keys.add(new PGPSecretKey(keyPair.getPrivateKey(), new PGPPublicKey(keyPair.getPublicKey(), null, subSigs), checksumCalculator, keyEncryptor)); + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("exception adding subkey: ", e); + } + } + + /** + * Return the secret key ring. + * + * @return a secret key ring. + */ + public PGPSecretKeyRing generateSecretKeyRing() + { + return new PGPSecretKeyRing(keys); + } + + /** + * Return the public key ring that corresponds to the secret key ring. + * + * @return a public key ring. + */ + public PGPPublicKeyRing generatePublicKeyRing() + { + Iterator it = keys.iterator(); + List pubKeys = new ArrayList(); + + pubKeys.add(((PGPSecretKey)it.next()).getPublicKey()); + + while (it.hasNext()) + { + PGPPublicKey k = new PGPPublicKey(((PGPSecretKey)it.next()).getPublicKey()); + + k.publicPk = new PublicSubkeyPacket(k.getAlgorithm(), k.getCreationTime(), k.publicPk.getKey()); + + pubKeys.add(k); + } + + return new PGPPublicKeyRing(pubKeys); + } + + private static PGPDigestCalculator convertSHA1Flag(boolean useSHA1) + throws PGPException + { + return useSHA1 ? new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1) : null; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyValidationException.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyValidationException.java new file mode 100644 index 000000000..63245c519 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPKeyValidationException.java @@ -0,0 +1,16 @@ +package org.spongycastle.openpgp; + +/** + * Thrown if the key checksum is invalid. + */ +public class PGPKeyValidationException + extends PGPException +{ + /** + * @param message + */ + public PGPKeyValidationException(String message) + { + super(message); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralData.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralData.java new file mode 100644 index 000000000..e6ac1bc62 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralData.java @@ -0,0 +1,96 @@ +package org.spongycastle.openpgp; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.LiteralDataPacket; + +/** + * class for processing literal data objects. + */ +public class PGPLiteralData +{ + public static final char BINARY = 'b'; + public static final char TEXT = 't'; + public static final char UTF8 = 'u'; + + /** + * The special name indicating a "for your eyes only" packet. + */ + public static final String CONSOLE = "_CONSOLE"; + + /** + * The special time for a modification time of "now" or + * the present time. + */ + public static final Date NOW = new Date(0L); + + LiteralDataPacket data; + + public PGPLiteralData( + BCPGInputStream pIn) + throws IOException + { + data = (LiteralDataPacket)pIn.readPacket(); + } + + /** + * Return the format of the data stream - BINARY or TEXT. + * + * @return int + */ + public int getFormat() + { + return data.getFormat(); + } + + /** + * Return the file name that's associated with the data stream. + * + * @return String + */ + public String getFileName() + { + return data.getFileName(); + } + + /** + * Return the file name as an unintrepreted byte array. + */ + public byte[] getRawFileName() + { + return data.getRawFileName(); + } + + /** + * Return the modification time for the file. + * + * @return the modification time. + */ + public Date getModificationTime() + { + return new Date(data.getModificationTime()); + } + + /** + * Return the raw input stream for the data stream. + * + * @return InputStream + */ + public InputStream getInputStream() + { + return data.getInputStream(); + } + + /** + * Return the input stream representing the data stream + * + * @return InputStream + */ + public InputStream getDataStream() + { + return this.getInputStream(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralDataGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralDataGenerator.java new file mode 100644 index 000000000..4835be533 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPLiteralDataGenerator.java @@ -0,0 +1,202 @@ +package org.spongycastle.openpgp; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; + +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.PacketTags; +import org.spongycastle.util.Strings; + +/** + * Class for producing literal data packets. + */ +public class PGPLiteralDataGenerator implements StreamGenerator +{ + public static final char BINARY = PGPLiteralData.BINARY; + public static final char TEXT = PGPLiteralData.TEXT; + public static final char UTF8 = PGPLiteralData.UTF8; + + /** + * The special name indicating a "for your eyes only" packet. + */ + public static final String CONSOLE = PGPLiteralData.CONSOLE; + + /** + * The special time for a modification time of "now" or + * the present time. + */ + public static final Date NOW = PGPLiteralData.NOW; + + private BCPGOutputStream pkOut; + private boolean oldFormat = false; + + public PGPLiteralDataGenerator() + { + } + + /** + * Generates literal data objects in the old format, this is + * important if you need compatability with PGP 2.6.x. + * + * @param oldFormat + */ + public PGPLiteralDataGenerator( + boolean oldFormat) + { + this.oldFormat = oldFormat; + } + + private void writeHeader( + OutputStream out, + char format, + byte[] encName, + long modificationTime) + throws IOException + { + out.write(format); + + out.write((byte)encName.length); + + for (int i = 0; i != encName.length; i++) + { + out.write(encName[i]); + } + + long modDate = modificationTime / 1000; + + out.write((byte)(modDate >> 24)); + out.write((byte)(modDate >> 16)); + out.write((byte)(modDate >> 8)); + out.write((byte)(modDate)); + } + + /** + * Open a literal data packet, returning a stream to store the data inside + * the packet. + * <p> + * The stream created can be closed off by either calling close() + * on the stream or close() on the generator. Closing the returned + * stream does not close off the OutputStream parameter out. + * + * @param out the stream we want the packet in + * @param format the format we are using + * @param name the name of the "file" + * @param length the length of the data we will write + * @param modificationTime the time of last modification we want stored. + */ + public OutputStream open( + OutputStream out, + char format, + String name, + long length, + Date modificationTime) + throws IOException + { + if (pkOut != null) + { + throw new IllegalStateException("generator already in open state"); + } + + byte[] encName = Strings.toUTF8ByteArray(name); + + pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, length + 2 + encName.length + 4, oldFormat); + + writeHeader(pkOut, format, encName, modificationTime.getTime()); + + return new WrappedGeneratorStream(pkOut, this); + } + + /** + * Open a literal data packet, returning a stream to store the data inside + * the packet as an indefinite length stream. The stream is written out as a + * series of partial packets with a chunk size determined by the size of the + * passed in buffer. + * <p> + * The stream created can be closed off by either calling close() + * on the stream or close() on the generator. Closing the returned + * stream does not close off the OutputStream parameter out. + * <p> + * <b>Note</b>: if the buffer is not a power of 2 in length only the largest power of 2 + * bytes worth of the buffer will be used. + * + * @param out the stream we want the packet in + * @param format the format we are using + * @param name the name of the "file" + * @param modificationTime the time of last modification we want stored. + * @param buffer the buffer to use for collecting data to put into chunks. + */ + public OutputStream open( + OutputStream out, + char format, + String name, + Date modificationTime, + byte[] buffer) + throws IOException + { + if (pkOut != null) + { + throw new IllegalStateException("generator already in open state"); + } + + pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, buffer); + + byte[] encName = Strings.toUTF8ByteArray(name); + + writeHeader(pkOut, format, encName, modificationTime.getTime()); + + return new WrappedGeneratorStream(pkOut, this); + } + + /** + * Open a literal data packet for the passed in File object, returning + * an output stream for saving the file contents. + * <p> + * The stream created can be closed off by either calling close() + * on the stream or close() on the generator. Closing the returned + * stream does not close off the OutputStream parameter out. + * + * @param out + * @param format + * @param file + * @return OutputStream + * @throws IOException + */ + public OutputStream open( + OutputStream out, + char format, + File file) + throws IOException + { + if (pkOut != null) + { + throw new IllegalStateException("generator already in open state"); + } + + byte[] encName = Strings.toUTF8ByteArray(file.getName()); + + pkOut = new BCPGOutputStream(out, PacketTags.LITERAL_DATA, file.length() + 2 + encName.length + 4, oldFormat); + + writeHeader(pkOut, format, encName, file.lastModified()); + + return new WrappedGeneratorStream(pkOut, this); + } + + /** + * Close the literal data packet - this is equivalent to calling close on the stream + * returned by the open() method. + * + * @throws IOException + */ + public void close() + throws IOException + { + if (pkOut != null) + { + pkOut.finish(); + pkOut.flush(); + pkOut = null; + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPMarker.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPMarker.java new file mode 100644 index 000000000..5719e90dc --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPMarker.java @@ -0,0 +1,34 @@ +/* + * Created on Mar 6, 2004 + * + * To change this generated comment go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ +package org.spongycastle.openpgp; + +import java.io.IOException; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.MarkerPacket; + +/** + * a PGP marker packet - in general these should be ignored other than where + * the idea is to preserve the original input stream. + */ +public class PGPMarker +{ + private MarkerPacket p; + + /** + * Default constructor. + * + * @param in + * @throws IOException + */ + public PGPMarker( + BCPGInputStream in) + throws IOException + { + p = (MarkerPacket)in.readPacket(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPObjectFactory.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPObjectFactory.java new file mode 100644 index 000000000..11fb3a53c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPObjectFactory.java @@ -0,0 +1,151 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.PacketTags; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; + +/** + * General class for reading a PGP object stream. + * <p> + * Note: if this class finds a PGPPublicKey or a PGPSecretKey it + * will create a PGPPublicKeyRing, or a PGPSecretKeyRing for each + * key found. If all you are trying to do is read a key ring file use + * either PGPPublicKeyRingCollection or PGPSecretKeyRingCollection. + */ +public class PGPObjectFactory +{ + private BCPGInputStream in; + private KeyFingerPrintCalculator fingerPrintCalculator; + + public PGPObjectFactory( + InputStream in) + { + this(in, new JcaKeyFingerprintCalculator()); + } + + /** + * Create an object factor suitable for reading keys, key rings and key ring collections. + * + * @param in stream to read from + * @param fingerPrintCalculator calculator to use in key finger print calculations. + */ + public PGPObjectFactory( + InputStream in, + KeyFingerPrintCalculator fingerPrintCalculator) + { + this.in = new BCPGInputStream(in); + this.fingerPrintCalculator = fingerPrintCalculator; + } + + public PGPObjectFactory( + byte[] bytes) + { + this(new ByteArrayInputStream(bytes)); + } + + /** + * Create an object factor suitable for reading keys, key rings and key ring collections. + * + * @param bytes stream to read from + * @param fingerPrintCalculator calculator to use in key finger print calculations. + */ + public PGPObjectFactory( + byte[] bytes, + KeyFingerPrintCalculator fingerPrintCalculator) + { + this(new ByteArrayInputStream(bytes), fingerPrintCalculator); + } + + /** + * Return the next object in the stream, or null if the end is reached. + * + * @return Object + * @throws IOException on a parse error + */ + public Object nextObject() + throws IOException + { + List l; + + switch (in.nextPacketTag()) + { + case -1: + return null; + case PacketTags.SIGNATURE: + l = new ArrayList(); + + while (in.nextPacketTag() == PacketTags.SIGNATURE) + { + try + { + l.add(new PGPSignature(in)); + } + catch (PGPException e) + { + throw new IOException("can't create signature object: " + e); + } + } + + return new PGPSignatureList((PGPSignature[])l.toArray(new PGPSignature[l.size()])); + case PacketTags.SECRET_KEY: + try + { + return new PGPSecretKeyRing(in, fingerPrintCalculator); + } + catch (PGPException e) + { + throw new IOException("can't create secret key object: " + e); + } + case PacketTags.PUBLIC_KEY: + return new PGPPublicKeyRing(in, fingerPrintCalculator); + case PacketTags.PUBLIC_SUBKEY: + try + { + return PGPPublicKeyRing.readSubkey(in, fingerPrintCalculator); + } + catch (PGPException e) + { + throw new IOException("processing error: " + e.getMessage()); + } + case PacketTags.COMPRESSED_DATA: + return new PGPCompressedData(in); + case PacketTags.LITERAL_DATA: + return new PGPLiteralData(in); + case PacketTags.PUBLIC_KEY_ENC_SESSION: + case PacketTags.SYMMETRIC_KEY_ENC_SESSION: + return new PGPEncryptedDataList(in); + case PacketTags.ONE_PASS_SIGNATURE: + l = new ArrayList(); + + while (in.nextPacketTag() == PacketTags.ONE_PASS_SIGNATURE) + { + try + { + l.add(new PGPOnePassSignature(in)); + } + catch (PGPException e) + { + throw new IOException("can't create one pass signature object: " + e); + } + } + + return new PGPOnePassSignatureList((PGPOnePassSignature[])l.toArray(new PGPOnePassSignature[l.size()])); + case PacketTags.MARKER: + return new PGPMarker(in); + case PacketTags.EXPERIMENTAL_1: + case PacketTags.EXPERIMENTAL_2: + case PacketTags.EXPERIMENTAL_3: + case PacketTags.EXPERIMENTAL_4: + return in.readPacket(); + } + + throw new IOException("unknown object in stream: " + in.nextPacketTag()); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignature.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignature.java new file mode 100644 index 000000000..567edabe6 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignature.java @@ -0,0 +1,265 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SignatureException; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.OnePassSignaturePacket; +import org.spongycastle.openpgp.operator.PGPContentVerifier; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; + +/** + * A one pass signature object. + */ +public class PGPOnePassSignature +{ + private OnePassSignaturePacket sigPack; + private int signatureType; + + private PGPContentVerifier verifier; + private byte lastb; + private OutputStream sigOut; + + PGPOnePassSignature( + BCPGInputStream pIn) + throws IOException, PGPException + { + this((OnePassSignaturePacket)pIn.readPacket()); + } + + PGPOnePassSignature( + OnePassSignaturePacket sigPack) + throws PGPException + { + this.sigPack = sigPack; + this.signatureType = sigPack.getSignatureType(); + } + + /** + * Initialise the signature object for verification. + * + * @param pubKey + * @param provider + * @throws NoSuchProviderException + * @throws PGPException + * @deprecated use init() method. + */ + public void initVerify( + PGPPublicKey pubKey, + String provider) + throws NoSuchProviderException, PGPException + { + initVerify(pubKey, PGPUtil.getProvider(provider)); + } + + /** + * Initialise the signature object for verification. + * + * @param pubKey + * @param provider + * @throws PGPException + * @deprecated use init() method. + */ + public void initVerify( + PGPPublicKey pubKey, + Provider provider) + throws PGPException + { + init(new JcaPGPContentVerifierBuilderProvider().setProvider(provider), pubKey); + } + + /** + * Initialise the signature object for verification. + * + * @param verifierBuilderProvider provider for a content verifier builder for the signature type of interest. + * @param pubKey the public key to use for verification + * @throws PGPException if there's an issue with creating the verifier. + */ + public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPPublicKey pubKey) + throws PGPException + { + PGPContentVerifierBuilder verifierBuilder = verifierBuilderProvider.get(sigPack.getKeyAlgorithm(), sigPack.getHashAlgorithm()); + + verifier = verifierBuilder.build(pubKey); + + lastb = 0; + sigOut = verifier.getOutputStream(); + } + + public void update( + byte b) + throws SignatureException + { + if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + if (b == '\r') + { + byteUpdate((byte)'\r'); + byteUpdate((byte)'\n'); + } + else if (b == '\n') + { + if (lastb != '\r') + { + byteUpdate((byte)'\r'); + byteUpdate((byte)'\n'); + } + } + else + { + byteUpdate(b); + } + + lastb = b; + } + else + { + byteUpdate(b); + } + } + + public void update( + byte[] bytes) + throws SignatureException + { + if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + for (int i = 0; i != bytes.length; i++) + { + this.update(bytes[i]); + } + } + else + { + blockUpdate(bytes, 0, bytes.length); + } + } + + public void update( + byte[] bytes, + int off, + int length) + throws SignatureException + { + if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + int finish = off + length; + + for (int i = off; i != finish; i++) + { + this.update(bytes[i]); + } + } + else + { + blockUpdate(bytes, off, length); + } + } + + private void byteUpdate(byte b) + throws SignatureException + { + try + { + sigOut.write(b); + } + catch (IOException e) + { // TODO: we really should get rid of signature exception next.... + throw new SignatureException(e.getMessage()); + } + } + + private void blockUpdate(byte[] block, int off, int len) + throws SignatureException + { + try + { + sigOut.write(block, off, len); + } + catch (IOException e) + { + throw new IllegalStateException(e.getMessage()); + } + } + + /** + * Verify the calculated signature against the passed in PGPSignature. + * + * @param pgpSig + * @return boolean + * @throws PGPException + * @throws SignatureException + */ + public boolean verify( + PGPSignature pgpSig) + throws PGPException, SignatureException + { + try + { + sigOut.write(pgpSig.getSignatureTrailer()); + + sigOut.close(); + } + catch (IOException e) + { + throw new PGPException("unable to add trailer: " + e.getMessage(), e); + } + + return verifier.verify(pgpSig.getSignature()); + } + + public long getKeyID() + { + return sigPack.getKeyID(); + } + + public int getSignatureType() + { + return sigPack.getSignatureType(); + } + + public int getHashAlgorithm() + { + return sigPack.getHashAlgorithm(); + } + + public int getKeyAlgorithm() + { + return sigPack.getKeyAlgorithm(); + } + + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + this.encode(bOut); + + return bOut.toByteArray(); + } + + public void encode( + OutputStream outStream) + throws IOException + { + BCPGOutputStream out; + + if (outStream instanceof BCPGOutputStream) + { + out = (BCPGOutputStream)outStream; + } + else + { + out = new BCPGOutputStream(outStream); + } + + out.writePacket(sigPack); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignatureList.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignatureList.java new file mode 100644 index 000000000..471c64e91 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPOnePassSignatureList.java @@ -0,0 +1,40 @@ +package org.spongycastle.openpgp; + +/** + * Holder for a list of PGPOnePassSignatures + */ +public class PGPOnePassSignatureList +{ + PGPOnePassSignature[] sigs; + + public PGPOnePassSignatureList( + PGPOnePassSignature[] sigs) + { + this.sigs = new PGPOnePassSignature[sigs.length]; + + System.arraycopy(sigs, 0, this.sigs, 0, sigs.length); + } + + public PGPOnePassSignatureList( + PGPOnePassSignature sig) + { + this.sigs = new PGPOnePassSignature[1]; + this.sigs[0] = sig; + } + + public PGPOnePassSignature get( + int index) + { + return sigs[index]; + } + + public int size() + { + return sigs.length; + } + + public boolean isEmpty() + { + return (sigs.length == 0); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPBEEncryptedData.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPBEEncryptedData.java new file mode 100644 index 000000000..b728eae8d --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPBEEncryptedData.java @@ -0,0 +1,180 @@ +package org.spongycastle.openpgp; + +import java.io.EOFException; +import java.io.InputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.InputStreamPacket; +import org.spongycastle.bcpg.SymmetricEncIntegrityPacket; +import org.spongycastle.bcpg.SymmetricKeyEncSessionPacket; +import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.spongycastle.util.io.TeeInputStream; + +/** + * A password based encryption object. + */ +public class PGPPBEEncryptedData + extends PGPEncryptedData +{ + SymmetricKeyEncSessionPacket keyData; + + PGPPBEEncryptedData( + SymmetricKeyEncSessionPacket keyData, + InputStreamPacket encData) + { + super(encData); + + this.keyData = keyData; + } + + /** + * Return the raw input stream for the data stream. + * + * @return InputStream + */ + public InputStream getInputStream() + { + return encData.getInputStream(); + } + + /** + * Return the decrypted input stream, using the passed in passPhrase. + * + * @param passPhrase + * @param provider + * @return InputStream + * @throws PGPException + * @throws NoSuchProviderException + * @deprecated use PBEDataDecryptorFactory method + */ + public InputStream getDataStream( + char[] passPhrase, + String provider) + throws PGPException, NoSuchProviderException + { + return getDataStream(passPhrase, PGPUtil.getProvider(provider)); + } + + /** + * Return the decrypted input stream, using the passed in passPhrase. + * + * @param passPhrase + * @param provider + * @return InputStream + * @throws PGPException + * @deprecated use PBEDataDecryptorFactory method + */ + public InputStream getDataStream( + char[] passPhrase, + Provider provider) + throws PGPException + { + return getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build()).setProvider(provider).build(passPhrase)); + } + + /** + * Return the symmetric key algorithm required to decrypt the data protected by this object. + * + * @param dataDecryptorFactory decryptor factory to use to recover the session data. + * @return the integer encryption algorithm code. + * @throws PGPException if the session data cannot be recovered. + */ + public int getSymmetricAlgorithm( + PBEDataDecryptorFactory dataDecryptorFactory) + throws PGPException + { + byte[] key = dataDecryptorFactory.makeKeyFromPassPhrase(keyData.getEncAlgorithm(), keyData.getS2K()); + byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getEncAlgorithm(), key, keyData.getSecKeyData()); + + return sessionData[0]; + } + + /** + * Open an input stream which will provide the decrypted data protected by this object. + * + * @param dataDecryptorFactory decryptor factory to use to recover the session data and provide the stream. + * @return the resulting input stream + * @throws PGPException if the session data cannot be recovered or the stream cannot be created. + */ + public InputStream getDataStream( + PBEDataDecryptorFactory dataDecryptorFactory) + throws PGPException + { + try + { + int keyAlgorithm = keyData.getEncAlgorithm(); + byte[] key = dataDecryptorFactory.makeKeyFromPassPhrase(keyAlgorithm, keyData.getS2K()); + boolean withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket; + + byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getEncAlgorithm(), key, keyData.getSecKeyData()); + byte[] sessionKey = new byte[sessionData.length - 1]; + + System.arraycopy(sessionData, 1, sessionKey, 0, sessionKey.length); + + PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionData[0] & 0xff, sessionKey); + + encStream = new BCPGInputStream(dataDecryptor.getInputStream(encData.getInputStream())); + + if (withIntegrityPacket) + { + truncStream = new TruncatedStream(encStream); + + integrityCalculator = dataDecryptor.getIntegrityCalculator(); + + encStream = new TeeInputStream(truncStream, integrityCalculator.getOutputStream()); + } + + byte[] iv = new byte[dataDecryptor.getBlockSize()]; + for (int i = 0; i != iv.length; i++) + { + int ch = encStream.read(); + + if (ch < 0) + { + throw new EOFException("unexpected end of stream."); + } + + iv[i] = (byte)ch; + } + + int v1 = encStream.read(); + int v2 = encStream.read(); + + if (v1 < 0 || v2 < 0) + { + throw new EOFException("unexpected end of stream."); + } + + + // Note: the oracle attack on "quick check" bytes is not deemed + // a security risk for PBE (see PGPPublicKeyEncryptedData) + + boolean repeatCheckPassed = iv[iv.length - 2] == (byte) v1 + && iv[iv.length - 1] == (byte) v2; + + // Note: some versions of PGP appear to produce 0 for the extra + // bytes rather than repeating the two previous bytes + boolean zeroesCheckPassed = v1 == 0 && v2 == 0; + + if (!repeatCheckPassed && !zeroesCheckPassed) + { + throw new PGPDataValidationException("data check failed."); + } + + return encStream; + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception creating cipher", e); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPrivateKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPrivateKey.java new file mode 100644 index 000000000..a62cf3495 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPrivateKey.java @@ -0,0 +1,138 @@ +package org.spongycastle.openpgp; + +import java.security.PrivateKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.RSAPrivateCrtKey; + +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.DSASecretBCPGKey; +import org.spongycastle.bcpg.ElGamalSecretBCPGKey; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSASecretBCPGKey; +import org.spongycastle.jce.interfaces.ElGamalPrivateKey; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; + +/** + * general class to contain a private key for use with other openPGP + * objects. + */ +public class PGPPrivateKey +{ + private long keyID; + private PrivateKey privateKey; + private PublicKeyPacket publicKeyPacket; + private BCPGKey privateKeyDataPacket; + + /** + * Create a PGPPrivateKey from a regular private key and the keyID of its associated + * public key. + * + * @param privateKey private key tu use. + * @param keyID keyID of the corresponding public key. + * @deprecated use JcaPGPKeyConverter + */ + public PGPPrivateKey( + PrivateKey privateKey, + long keyID) + { + this.privateKey = privateKey; + this.keyID = keyID; + + if (privateKey instanceof RSAPrivateCrtKey) + { + RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privateKey; + + privateKeyDataPacket = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ()); + } + else if (privateKey instanceof DSAPrivateKey) + { + DSAPrivateKey dsK = (DSAPrivateKey)privateKey; + + privateKeyDataPacket = new DSASecretBCPGKey(dsK.getX()); + } + else if (privateKey instanceof ElGamalPrivateKey) + { + ElGamalPrivateKey esK = (ElGamalPrivateKey)privateKey; + + privateKeyDataPacket = new ElGamalSecretBCPGKey(esK.getX()); + } + else + { + throw new IllegalArgumentException("unknown key class"); + } + + } + + /** + * Base constructor. + * + * Create a PGPPrivateKey from a keyID and the associated public/private data packets needed + * to fully describe it. + * + * @param keyID keyID associated with the public key. + * @param publicKeyPacket the public key data packet to be associated with this private key. + * @param privateKeyDataPacket the private key data packet to be associate with this private key. + */ + public PGPPrivateKey( + long keyID, + PublicKeyPacket publicKeyPacket, + BCPGKey privateKeyDataPacket) + { + this.keyID = keyID; + this.publicKeyPacket = publicKeyPacket; + this.privateKeyDataPacket = privateKeyDataPacket; + } + + /** + * Return the keyID associated with the contained private key. + * + * @return long + */ + public long getKeyID() + { + return keyID; + } + + /** + * Return the contained private key. + * + * @return PrivateKey + * @deprecated use a JcaPGPKeyConverter + */ + public PrivateKey getKey() + { + if (privateKey != null) + { + return privateKey; + } + + try + { + return new JcaPGPKeyConverter().setProvider(PGPUtil.getDefaultProvider()).getPrivateKey(this); + } + catch (PGPException e) + { + throw new IllegalStateException("unable to convert key: " + e.toString()); + } + } + + /** + * Return the public key packet associated with this private key, if available. + * + * @return associated public key packet, null otherwise. + */ + public PublicKeyPacket getPublicKeyPacket() + { + return publicKeyPacket; + } + + /** + * Return the private key packet associated with this private key, if available. + * + * @return associated private key packet, null otherwise. + */ + public BCPGKey getPrivateKeyDataPacket() + { + return privateKeyDataPacket; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKey.java new file mode 100644 index 000000000..ba6d6fe92 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKey.java @@ -0,0 +1,964 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.ContainedPacket; +import org.spongycastle.bcpg.DSAPublicBCPGKey; +import org.spongycastle.bcpg.ElGamalPublicBCPGKey; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.bcpg.TrustPacket; +import org.spongycastle.bcpg.UserAttributePacket; +import org.spongycastle.bcpg.UserIDPacket; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; +import org.spongycastle.util.Arrays; + +/** + * general class to handle a PGP public key object. + */ +public class PGPPublicKey + implements PublicKeyAlgorithmTags +{ + private static final int[] MASTER_KEY_CERTIFICATION_TYPES = new int[] { PGPSignature.POSITIVE_CERTIFICATION, PGPSignature.CASUAL_CERTIFICATION, PGPSignature.NO_CERTIFICATION, PGPSignature.DEFAULT_CERTIFICATION }; + + PublicKeyPacket publicPk; + TrustPacket trustPk; + List keySigs = new ArrayList(); + List ids = new ArrayList(); + List idTrusts = new ArrayList(); + List idSigs = new ArrayList(); + + List subSigs = null; + + private long keyID; + private byte[] fingerprint; + private int keyStrength; + + private void init(KeyFingerPrintCalculator fingerPrintCalculator) + throws PGPException + { + BCPGKey key = publicPk.getKey(); + + this.fingerprint = fingerPrintCalculator.calculateFingerprint(publicPk); + + if (publicPk.getVersion() <= 3) + { + RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key; + + this.keyID = rK.getModulus().longValue(); + this.keyStrength = rK.getModulus().bitLength(); + } + else + { + this.keyID = ((long)(fingerprint[fingerprint.length - 8] & 0xff) << 56) + | ((long)(fingerprint[fingerprint.length - 7] & 0xff) << 48) + | ((long)(fingerprint[fingerprint.length - 6] & 0xff) << 40) + | ((long)(fingerprint[fingerprint.length - 5] & 0xff) << 32) + | ((long)(fingerprint[fingerprint.length - 4] & 0xff) << 24) + | ((long)(fingerprint[fingerprint.length - 3] & 0xff) << 16) + | ((long)(fingerprint[fingerprint.length - 2] & 0xff) << 8) + | ((fingerprint[fingerprint.length - 1] & 0xff)); + + if (key instanceof RSAPublicBCPGKey) + { + this.keyStrength = ((RSAPublicBCPGKey)key).getModulus().bitLength(); + } + else if (key instanceof DSAPublicBCPGKey) + { + this.keyStrength = ((DSAPublicBCPGKey)key).getP().bitLength(); + } + else if (key instanceof ElGamalPublicBCPGKey) + { + this.keyStrength = ((ElGamalPublicBCPGKey)key).getP().bitLength(); + } + } + } + + /** + * Create a PGPPublicKey from the passed in JCA one. + * <p> + * Note: the time passed in affects the value of the key's keyID, so you probably only want + * to do this once for a JCA key, or make sure you keep track of the time you used. + * + * @param algorithm asymmetric algorithm type representing the public key. + * @param pubKey actual public key to associate. + * @param time date of creation. + * @param provider provider to use for underlying digest calculations. + * @throws PGPException on key creation problem. + * @throws NoSuchProviderException if the specified provider is required and cannot be found. + * @deprecated use JcaPGPKeyConverter.getPGPPublicKey() + */ + public PGPPublicKey( + int algorithm, + PublicKey pubKey, + Date time, + String provider) + throws PGPException, NoSuchProviderException + { + this(new JcaPGPKeyConverter().setProvider(provider).getPGPPublicKey(algorithm, pubKey, time)); + } + + /** + * @deprecated use JcaPGPKeyConverter.getPGPPublicKey() + */ + public PGPPublicKey( + int algorithm, + PublicKey pubKey, + Date time) + throws PGPException + { + this(new JcaPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, time)); + } + + /** + * Create a PGP public key from a packet descriptor using the passed in fingerPrintCalculator to do calculate + * the fingerprint and keyID. + * + * @param publicKeyPacket packet describing the public key. + * @param fingerPrintCalculator calculator providing the digest support ot create the key fingerprint. + * @throws PGPException if the packet is faulty, or the required calculations fail. + */ + public PGPPublicKey(PublicKeyPacket publicKeyPacket, KeyFingerPrintCalculator fingerPrintCalculator) + throws PGPException + { + this.publicPk = publicKeyPacket; + this.ids = new ArrayList(); + this.idSigs = new ArrayList(); + + init(fingerPrintCalculator); + } + + /* + * Constructor for a sub-key. + */ + PGPPublicKey( + PublicKeyPacket publicPk, + TrustPacket trustPk, + List sigs, + KeyFingerPrintCalculator fingerPrintCalculator) + throws PGPException + { + this.publicPk = publicPk; + this.trustPk = trustPk; + this.subSigs = sigs; + + init(fingerPrintCalculator); + } + + PGPPublicKey( + PGPPublicKey key, + TrustPacket trust, + List subSigs) + { + this.publicPk = key.publicPk; + this.trustPk = trust; + this.subSigs = subSigs; + + this.fingerprint = key.fingerprint; + this.keyID = key.keyID; + this.keyStrength = key.keyStrength; + } + + /** + * Copy constructor. + * @param pubKey the public key to copy. + */ + PGPPublicKey( + PGPPublicKey pubKey) + { + this.publicPk = pubKey.publicPk; + + this.keySigs = new ArrayList(pubKey.keySigs); + this.ids = new ArrayList(pubKey.ids); + this.idTrusts = new ArrayList(pubKey.idTrusts); + this.idSigs = new ArrayList(pubKey.idSigs.size()); + for (int i = 0; i != pubKey.idSigs.size(); i++) + { + this.idSigs.add(new ArrayList((ArrayList)pubKey.idSigs.get(i))); + } + + if (pubKey.subSigs != null) + { + this.subSigs = new ArrayList(pubKey.subSigs.size()); + for (int i = 0; i != pubKey.subSigs.size(); i++) + { + this.subSigs.add(pubKey.subSigs.get(i)); + } + } + + this.fingerprint = pubKey.fingerprint; + this.keyID = pubKey.keyID; + this.keyStrength = pubKey.keyStrength; + } + + PGPPublicKey( + PublicKeyPacket publicPk, + TrustPacket trustPk, + List keySigs, + List ids, + List idTrusts, + List idSigs, + KeyFingerPrintCalculator fingerPrintCalculator) + throws PGPException + { + this.publicPk = publicPk; + this.trustPk = trustPk; + this.keySigs = keySigs; + this.ids = ids; + this.idTrusts = idTrusts; + this.idSigs = idSigs; + + init(fingerPrintCalculator); + } + + /** + * @return the version of this key. + */ + public int getVersion() + { + return publicPk.getVersion(); + } + + /** + * @return creation time of key. + */ + public Date getCreationTime() + { + return publicPk.getTime(); + } + + /** + * @return number of valid days from creation time - zero means no + * expiry. + */ + public int getValidDays() + { + if (publicPk.getVersion() > 3) + { + return (int)(this.getValidSeconds() / (24 * 60 * 60)); + } + else + { + return publicPk.getValidDays(); + } + } + + /** + * Return the trust data associated with the public key, if present. + * @return a byte array with trust data, null otherwise. + */ + public byte[] getTrustData() + { + if (trustPk == null) + { + return null; + } + + return Arrays.clone(trustPk.getLevelAndTrustAmount()); + } + + /** + * @return number of valid seconds from creation time - zero means no + * expiry. + */ + public long getValidSeconds() + { + if (publicPk.getVersion() > 3) + { + if (this.isMasterKey()) + { + for (int i = 0; i != MASTER_KEY_CERTIFICATION_TYPES.length; i++) + { + long seconds = getExpirationTimeFromSig(true, MASTER_KEY_CERTIFICATION_TYPES[i]); + + if (seconds >= 0) + { + return seconds; + } + } + } + else + { + long seconds = getExpirationTimeFromSig(false, PGPSignature.SUBKEY_BINDING); + + if (seconds >= 0) + { + return seconds; + } + } + + return 0; + } + else + { + return (long)publicPk.getValidDays() * 24 * 60 * 60; + } + } + + private long getExpirationTimeFromSig( + boolean selfSigned, + int signatureType) + { + Iterator signatures = this.getSignaturesOfType(signatureType); + long expiryTime = -1; + + while (signatures.hasNext()) + { + PGPSignature sig = (PGPSignature)signatures.next(); + + if (!selfSigned || sig.getKeyID() == this.getKeyID()) + { + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + + if (hashed != null) + { + long current = hashed.getKeyExpirationTime(); + + if (current == 0 || current > expiryTime) + { + expiryTime = current; + } + } + else + { + return 0; + } + } + } + + return expiryTime; + } + + /** + * Return the keyID associated with the public key. + * + * @return long + */ + public long getKeyID() + { + return keyID; + } + + /** + * Return the fingerprint of the key. + * + * @return key fingerprint. + */ + public byte[] getFingerprint() + { + byte[] tmp = new byte[fingerprint.length]; + + System.arraycopy(fingerprint, 0, tmp, 0, tmp.length); + + return tmp; + } + + /** + * Return true if this key has an algorithm type that makes it suitable to use for encryption. + * <p> + * Note: with version 4 keys KeyFlags subpackets should also be considered when present for + * determining the preferred use of the key. + * + * @return true if the key algorithm is suitable for encryption. + */ + public boolean isEncryptionKey() + { + int algorithm = publicPk.getAlgorithm(); + + return ((algorithm == RSA_GENERAL) || (algorithm == RSA_ENCRYPT) + || (algorithm == ELGAMAL_ENCRYPT) || (algorithm == ELGAMAL_GENERAL) || algorithm == ECDH); + } + + /** + * Return true if this is a master key. + * @return true if a master key. + */ + public boolean isMasterKey() + { + return (subSigs == null); + } + + /** + * Return the algorithm code associated with the public key. + * + * @return int + */ + public int getAlgorithm() + { + return publicPk.getAlgorithm(); + } + + /** + * Return the strength of the key in bits. + * + * @return bit strenght of key. + */ + public int getBitStrength() + { + return keyStrength; + } + + /** + * Return the public key contained in the object. + * + * @param provider provider to construct the key for. + * @return a JCE/JCA public key. + * @throws PGPException if the key algorithm is not recognised. + * @throws NoSuchProviderException if the provider cannot be found. + * @deprecated use a JcaPGPKeyConverter + */ + public PublicKey getKey( + String provider) + throws PGPException, NoSuchProviderException + { + return new JcaPGPKeyConverter().setProvider(provider).getPublicKey(this); + } + + /** + * Return the public key contained in the object. + * + * @param provider provider to construct the key for. + * @return a JCE/JCA public key. + * @throws PGPException if the key algorithm is not recognised. + * @deprecated use a JcaPGPKeyConverter + */ + public PublicKey getKey( + Provider provider) + throws PGPException + { + return new JcaPGPKeyConverter().setProvider(provider).getPublicKey(this); + } + + /** + * Return any userIDs associated with the key. + * + * @return an iterator of Strings. + */ + public Iterator getUserIDs() + { + List temp = new ArrayList(); + + for (int i = 0; i != ids.size(); i++) + { + if (ids.get(i) instanceof String) + { + temp.add(ids.get(i)); + } + } + + return temp.iterator(); + } + + /** + * Return any user attribute vectors associated with the key. + * + * @return an iterator of PGPUserAttributeSubpacketVector objects. + */ + public Iterator getUserAttributes() + { + List temp = new ArrayList(); + + for (int i = 0; i != ids.size(); i++) + { + if (ids.get(i) instanceof PGPUserAttributeSubpacketVector) + { + temp.add(ids.get(i)); + } + } + + return temp.iterator(); + } + + /** + * Return any signatures associated with the passed in id. + * + * @param id the id to be matched. + * @return an iterator of PGPSignature objects. + */ + public Iterator getSignaturesForID( + String id) + { + for (int i = 0; i != ids.size(); i++) + { + if (id.equals(ids.get(i))) + { + return ((ArrayList)idSigs.get(i)).iterator(); + } + } + + return null; + } + + /** + * Return an iterator of signatures associated with the passed in user attributes. + * + * @param userAttributes the vector of user attributes to be matched. + * @return an iterator of PGPSignature objects. + */ + public Iterator getSignaturesForUserAttribute( + PGPUserAttributeSubpacketVector userAttributes) + { + for (int i = 0; i != ids.size(); i++) + { + if (userAttributes.equals(ids.get(i))) + { + return ((ArrayList)idSigs.get(i)).iterator(); + } + } + + return null; + } + + /** + * Return signatures of the passed in type that are on this key. + * + * @param signatureType the type of the signature to be returned. + * @return an iterator (possibly empty) of signatures of the given type. + */ + public Iterator getSignaturesOfType( + int signatureType) + { + List l = new ArrayList(); + Iterator it = this.getSignatures(); + + while (it.hasNext()) + { + PGPSignature sig = (PGPSignature)it.next(); + + if (sig.getSignatureType() == signatureType) + { + l.add(sig); + } + } + + return l.iterator(); + } + + /** + * Return all signatures/certifications associated with this key. + * + * @return an iterator (possibly empty) with all signatures/certifications. + */ + public Iterator getSignatures() + { + if (subSigs == null) + { + List sigs = new ArrayList(); + + sigs.addAll(keySigs); + + for (int i = 0; i != idSigs.size(); i++) + { + sigs.addAll((Collection)idSigs.get(i)); + } + + return sigs.iterator(); + } + else + { + return subSigs.iterator(); + } + } + + public PublicKeyPacket getPublicKeyPacket() + { + return publicPk; + } + + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + this.encode(bOut); + + return bOut.toByteArray(); + } + + public void encode( + OutputStream outStream) + throws IOException + { + BCPGOutputStream out; + + if (outStream instanceof BCPGOutputStream) + { + out = (BCPGOutputStream)outStream; + } + else + { + out = new BCPGOutputStream(outStream); + } + + out.writePacket(publicPk); + if (trustPk != null) + { + out.writePacket(trustPk); + } + + if (subSigs == null) // not a sub-key + { + for (int i = 0; i != keySigs.size(); i++) + { + ((PGPSignature)keySigs.get(i)).encode(out); + } + + for (int i = 0; i != ids.size(); i++) + { + if (ids.get(i) instanceof String) + { + String id = (String)ids.get(i); + + out.writePacket(new UserIDPacket(id)); + } + else + { + PGPUserAttributeSubpacketVector v = (PGPUserAttributeSubpacketVector)ids.get(i); + + out.writePacket(new UserAttributePacket(v.toSubpacketArray())); + } + + if (idTrusts.get(i) != null) + { + out.writePacket((ContainedPacket)idTrusts.get(i)); + } + + List sigs = (List)idSigs.get(i); + for (int j = 0; j != sigs.size(); j++) + { + ((PGPSignature)sigs.get(j)).encode(out); + } + } + } + else + { + for (int j = 0; j != subSigs.size(); j++) + { + ((PGPSignature)subSigs.get(j)).encode(out); + } + } + } + + /** + * Check whether this (sub)key has a revocation signature on it. + * + * @return boolean indicating whether this (sub)key has been revoked. + */ + public boolean isRevoked() + { + int ns = 0; + boolean revoked = false; + + if (this.isMasterKey()) // Master key + { + while (!revoked && (ns < keySigs.size())) + { + if (((PGPSignature)keySigs.get(ns++)).getSignatureType() == PGPSignature.KEY_REVOCATION) + { + revoked = true; + } + } + } + else // Sub-key + { + while (!revoked && (ns < subSigs.size())) + { + if (((PGPSignature)subSigs.get(ns++)).getSignatureType() == PGPSignature.SUBKEY_REVOCATION) + { + revoked = true; + } + } + } + + return revoked; + } + + + /** + * Add a certification for an id to the given public key. + * + * @param key the key the certification is to be added to. + * @param id the id the certification is associated with. + * @param certification the new certification. + * @return the re-certified key. + */ + public static PGPPublicKey addCertification( + PGPPublicKey key, + String id, + PGPSignature certification) + { + return addCert(key, id, certification); + } + + /** + * Add a certification for the given UserAttributeSubpackets to the given public key. + * + * @param key the key the certification is to be added to. + * @param userAttributes the attributes the certification is associated with. + * @param certification the new certification. + * @return the re-certified key. + */ + public static PGPPublicKey addCertification( + PGPPublicKey key, + PGPUserAttributeSubpacketVector userAttributes, + PGPSignature certification) + { + return addCert(key, userAttributes, certification); + } + + private static PGPPublicKey addCert( + PGPPublicKey key, + Object id, + PGPSignature certification) + { + PGPPublicKey returnKey = new PGPPublicKey(key); + List sigList = null; + + for (int i = 0; i != returnKey.ids.size(); i++) + { + if (id.equals(returnKey.ids.get(i))) + { + sigList = (List)returnKey.idSigs.get(i); + } + } + + if (sigList != null) + { + sigList.add(certification); + } + else + { + sigList = new ArrayList(); + + sigList.add(certification); + returnKey.ids.add(id); + returnKey.idTrusts.add(null); + returnKey.idSigs.add(sigList); + } + + return returnKey; + } + + /** + * Remove any certifications associated with a given user attribute subpacket + * on a key. + * + * @param key the key the certifications are to be removed from. + * @param userAttributes the attributes to be removed. + * @return the re-certified key, null if the user attribute subpacket was not found on the key. + */ + public static PGPPublicKey removeCertification( + PGPPublicKey key, + PGPUserAttributeSubpacketVector userAttributes) + { + return removeCert(key, userAttributes); + } + + /** + * Remove any certifications associated with a given id on a key. + * + * @param key the key the certifications are to be removed from. + * @param id the id that is to be removed. + * @return the re-certified key, null if the id was not found on the key. + */ + public static PGPPublicKey removeCertification( + PGPPublicKey key, + String id) + { + return removeCert(key, id); + } + + private static PGPPublicKey removeCert( + PGPPublicKey key, + Object id) + { + PGPPublicKey returnKey = new PGPPublicKey(key); + boolean found = false; + + for (int i = 0; i < returnKey.ids.size(); i++) + { + if (id.equals(returnKey.ids.get(i))) + { + found = true; + returnKey.ids.remove(i); + returnKey.idTrusts.remove(i); + returnKey.idSigs.remove(i); + } + } + + if (!found) + { + return null; + } + + return returnKey; + } + + /** + * Remove a certification associated with a given id on a key. + * + * @param key the key the certifications are to be removed from. + * @param id the id that the certification is to be removed from. + * @param certification the certification to be removed. + * @return the re-certified key, null if the certification was not found. + */ + public static PGPPublicKey removeCertification( + PGPPublicKey key, + String id, + PGPSignature certification) + { + return removeCert(key, id, certification); + } + + /** + * Remove a certification associated with a given user attributes on a key. + * + * @param key the key the certifications are to be removed from. + * @param userAttributes the user attributes that the certification is to be removed from. + * @param certification the certification to be removed. + * @return the re-certified key, null if the certification was not found. + */ + public static PGPPublicKey removeCertification( + PGPPublicKey key, + PGPUserAttributeSubpacketVector userAttributes, + PGPSignature certification) + { + return removeCert(key, userAttributes, certification); + } + + private static PGPPublicKey removeCert( + PGPPublicKey key, + Object id, + PGPSignature certification) + { + PGPPublicKey returnKey = new PGPPublicKey(key); + boolean found = false; + + for (int i = 0; i < returnKey.ids.size(); i++) + { + if (id.equals(returnKey.ids.get(i))) + { + found = ((List)returnKey.idSigs.get(i)).remove(certification); + } + } + + if (!found) + { + return null; + } + + return returnKey; + } + + /** + * Add a revocation or some other key certification to a key. + * + * @param key the key the revocation is to be added to. + * @param certification the key signature to be added. + * @return the new changed public key object. + */ + public static PGPPublicKey addCertification( + PGPPublicKey key, + PGPSignature certification) + { + if (key.isMasterKey()) + { + if (certification.getSignatureType() == PGPSignature.SUBKEY_REVOCATION) + { + throw new IllegalArgumentException("signature type incorrect for master key revocation."); + } + } + else + { + if (certification.getSignatureType() == PGPSignature.KEY_REVOCATION) + { + throw new IllegalArgumentException("signature type incorrect for sub-key revocation."); + } + } + + PGPPublicKey returnKey = new PGPPublicKey(key); + + if (returnKey.subSigs != null) + { + returnKey.subSigs.add(certification); + } + else + { + returnKey.keySigs.add(certification); + } + + return returnKey; + } + + /** + * Remove a certification from the key. + * + * @param key the key the certifications are to be removed from. + * @param certification the certification to be removed. + * @return the modified key, null if the certification was not found. + */ + public static PGPPublicKey removeCertification( + PGPPublicKey key, + PGPSignature certification) + { + PGPPublicKey returnKey = new PGPPublicKey(key); + boolean found; + + if (returnKey.subSigs != null) + { + found = returnKey.subSigs.remove(certification); + } + else + { + found = returnKey.keySigs.remove(certification); + } + + if (!found) + { + for (Iterator it = key.getUserIDs(); it.hasNext();) + { + String id = (String)it.next(); + for (Iterator sIt = key.getSignaturesForID(id); sIt.hasNext();) + { + if (certification == sIt.next()) + { + found = true; + returnKey = PGPPublicKey.removeCertification(returnKey, id, certification); + } + } + } + + if (!found) + { + for (Iterator it = key.getUserAttributes(); it.hasNext();) + { + PGPUserAttributeSubpacketVector id = (PGPUserAttributeSubpacketVector)it.next(); + for (Iterator sIt = key.getSignaturesForUserAttribute(id); sIt.hasNext();) + { + if (certification == sIt.next()) + { + found = true; + returnKey = PGPPublicKey.removeCertification(returnKey, id, certification); + } + } + } + } + } + + return returnKey; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyEncryptedData.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyEncryptedData.java new file mode 100644 index 000000000..3baeda631 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyEncryptedData.java @@ -0,0 +1,262 @@ +package org.spongycastle.openpgp; + +import java.io.EOFException; +import java.io.InputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.InputStreamPacket; +import org.spongycastle.bcpg.PublicKeyEncSessionPacket; +import org.spongycastle.bcpg.SymmetricEncIntegrityPacket; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.spongycastle.util.io.TeeInputStream; + +/** + * A public key encrypted data object. + */ +public class PGPPublicKeyEncryptedData + extends PGPEncryptedData +{ + PublicKeyEncSessionPacket keyData; + + PGPPublicKeyEncryptedData( + PublicKeyEncSessionPacket keyData, + InputStreamPacket encData) + { + super(encData); + + this.keyData = keyData; + } + + private boolean confirmCheckSum( + byte[] sessionInfo) + { + int check = 0; + + for (int i = 1; i != sessionInfo.length - 2; i++) + { + check += sessionInfo[i] & 0xff; + } + + return (sessionInfo[sessionInfo.length - 2] == (byte)(check >> 8)) + && (sessionInfo[sessionInfo.length - 1] == (byte)(check)); + } + + /** + * Return the keyID for the key used to encrypt the data. + * + * @return long + */ + public long getKeyID() + { + return keyData.getKeyID(); + } + + /** + * Return the algorithm code for the symmetric algorithm used to encrypt the data. + * + * @return integer algorithm code + * @deprecated use the method taking a PublicKeyDataDecryptorFactory + */ + public int getSymmetricAlgorithm( + PGPPrivateKey privKey, + String provider) + throws PGPException, NoSuchProviderException + { + return getSymmetricAlgorithm(privKey, PGPUtil.getProvider(provider)); + } + + /** + * + * @deprecated use the method taking a PublicKeyDataDecryptorFactory + */ + public int getSymmetricAlgorithm( + PGPPrivateKey privKey, + Provider provider) + throws PGPException, NoSuchProviderException + { + return getSymmetricAlgorithm(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(provider).setContentProvider(provider).build(privKey)); + } + + /** + * Return the symmetric key algorithm required to decrypt the data protected by this object. + * + * @param dataDecryptorFactory decryptor factory to use to recover the session data. + * @return the integer encryption algorithm code. + * @throws PGPException if the session data cannot be recovered. + */ + public int getSymmetricAlgorithm( + PublicKeyDataDecryptorFactory dataDecryptorFactory) + throws PGPException + { + byte[] plain = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey()); + + return plain[0]; + } + + /** + * Return the decrypted data stream for the packet. + * + * @param privKey private key to use + * @param provider provider to use for private key and symmetric key decryption. + * @return InputStream + * @throws PGPException + * @throws NoSuchProviderException + * @deprecated use method that takes a PublicKeyDataDecryptorFactory + */ + public InputStream getDataStream( + PGPPrivateKey privKey, + String provider) + throws PGPException, NoSuchProviderException + { + return getDataStream(privKey, provider, provider); + } + + /** + * + * @param privKey + * @param provider + * @return + * @throws PGPException + * @deprecated use method that takes a PublicKeyDataDecryptorFactory + */ + public InputStream getDataStream( + PGPPrivateKey privKey, + Provider provider) + throws PGPException + { + return getDataStream(privKey, provider, provider); + } + + /** + * Return the decrypted data stream for the packet. + * + * @param privKey private key to use. + * @param asymProvider asymetric provider to use with private key. + * @param provider provider to use for symmetric algorithm. + * @return InputStream + * @throws PGPException + * @throws NoSuchProviderException + * @deprecated use method that takes a PublicKeyDataDecryptorFactory + */ + public InputStream getDataStream( + PGPPrivateKey privKey, + String asymProvider, + String provider) + throws PGPException, NoSuchProviderException + { + return getDataStream(privKey, PGPUtil.getProvider(asymProvider), PGPUtil.getProvider(provider)); + } + + /** + * @deprecated use method that takes a PublicKeyDataDecryptorFactory + */ + public InputStream getDataStream( + PGPPrivateKey privKey, + Provider asymProvider, + Provider provider) + throws PGPException + { + return getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(asymProvider).setContentProvider(provider).build(privKey)); + } + + /** + * Open an input stream which will provide the decrypted data protected by this object. + * + * @param dataDecryptorFactory decryptor factory to use to recover the session data and provide the stream. + * @return the resulting input stream + * @throws PGPException if the session data cannot be recovered or the stream cannot be created. + */ + public InputStream getDataStream( + PublicKeyDataDecryptorFactory dataDecryptorFactory) + throws PGPException + { + byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey()); + + if (!confirmCheckSum(sessionData)) + { + throw new PGPKeyValidationException("key checksum failed"); + } + + if (sessionData[0] != SymmetricKeyAlgorithmTags.NULL) + { + try + { + boolean withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket; + byte[] sessionKey = new byte[sessionData.length - 3]; + + System.arraycopy(sessionData, 1, sessionKey, 0, sessionKey.length); + + PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionData[0] & 0xff, sessionKey); + + encStream = new BCPGInputStream(dataDecryptor.getInputStream(encData.getInputStream())); + + if (withIntegrityPacket) + { + truncStream = new TruncatedStream(encStream); + + integrityCalculator = dataDecryptor.getIntegrityCalculator(); + + encStream = new TeeInputStream(truncStream, integrityCalculator.getOutputStream()); + } + + byte[] iv = new byte[dataDecryptor.getBlockSize()]; + + for (int i = 0; i != iv.length; i++) + { + int ch = encStream.read(); + + if (ch < 0) + { + throw new EOFException("unexpected end of stream."); + } + + iv[i] = (byte)ch; + } + + int v1 = encStream.read(); + int v2 = encStream.read(); + + if (v1 < 0 || v2 < 0) + { + throw new EOFException("unexpected end of stream."); + } + + // + // some versions of PGP appear to produce 0 for the extra + // bytes rather than repeating the two previous bytes + // + /* + * Commented out in the light of the oracle attack. + if (iv[iv.length - 2] != (byte)v1 && v1 != 0) + { + throw new PGPDataValidationException("data check failed."); + } + + if (iv[iv.length - 1] != (byte)v2 && v2 != 0) + { + throw new PGPDataValidationException("data check failed."); + } + */ + + return encStream; + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception starting decryption", e); + } + } + else + { + return encData.getInputStream(); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRing.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRing.java new file mode 100644 index 000000000..2358c207f --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRing.java @@ -0,0 +1,273 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.PacketTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.TrustPacket; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; + +/** + * Class to hold a single master public key and its subkeys. + * <p> + * Often PGP keyring files consist of multiple master keys, if you are trying to process + * or construct one of these you should use the PGPPublicKeyRingCollection class. + */ +public class PGPPublicKeyRing + extends PGPKeyRing +{ + List keys; + + /** + * @deprecated use version that takes a KeyFingerPrintCalculator + */ + public PGPPublicKeyRing( + byte[] encoding) + throws IOException + { + this(new ByteArrayInputStream(encoding), new JcaKeyFingerprintCalculator()); + } + + public PGPPublicKeyRing( + byte[] encoding, + KeyFingerPrintCalculator fingerPrintCalculator) + throws IOException + { + this(new ByteArrayInputStream(encoding), fingerPrintCalculator); + } + + /** + * @param pubKeys + */ + PGPPublicKeyRing( + List pubKeys) + { + this.keys = pubKeys; + } + + /** + * @deprecated use version that takes a KeyFingerPrintCalculator + */ + public PGPPublicKeyRing( + InputStream in) + throws IOException + { + this(in, new JcaKeyFingerprintCalculator()); + } + + public PGPPublicKeyRing( + InputStream in, + KeyFingerPrintCalculator fingerPrintCalculator) + throws IOException + { + this.keys = new ArrayList(); + + BCPGInputStream pIn = wrap(in); + + int initialTag = pIn.nextPacketTag(); + if (initialTag != PacketTags.PUBLIC_KEY && initialTag != PacketTags.PUBLIC_SUBKEY) + { + throw new IOException( + "public key ring doesn't start with public key tag: " + + "tag 0x" + Integer.toHexString(initialTag)); + } + + PublicKeyPacket pubPk = (PublicKeyPacket)pIn.readPacket(); + TrustPacket trustPk = readOptionalTrustPacket(pIn); + + // direct signatures and revocations + List keySigs = readSignaturesAndTrust(pIn); + + List ids = new ArrayList(); + List idTrusts = new ArrayList(); + List idSigs = new ArrayList(); + readUserIDs(pIn, ids, idTrusts, idSigs); + + try + { + keys.add(new PGPPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs, fingerPrintCalculator)); + + // Read subkeys + while (pIn.nextPacketTag() == PacketTags.PUBLIC_SUBKEY) + { + keys.add(readSubkey(pIn, fingerPrintCalculator)); + } + } + catch (PGPException e) + { + throw new IOException("processing exception: " + e.toString()); + } + } + + /** + * Return the first public key in the ring. + * + * @return PGPPublicKey + */ + public PGPPublicKey getPublicKey() + { + return (PGPPublicKey)keys.get(0); + } + + /** + * Return the public key referred to by the passed in keyID if it + * is present. + * + * @param keyID + * @return PGPPublicKey + */ + public PGPPublicKey getPublicKey( + long keyID) + { + for (int i = 0; i != keys.size(); i++) + { + PGPPublicKey k = (PGPPublicKey)keys.get(i); + + if (keyID == k.getKeyID()) + { + return k; + } + } + + return null; + } + + /** + * Return an iterator containing all the public keys. + * + * @return Iterator + */ + public Iterator getPublicKeys() + { + return Collections.unmodifiableList(keys).iterator(); + } + + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + this.encode(bOut); + + return bOut.toByteArray(); + } + + public void encode( + OutputStream outStream) + throws IOException + { + for (int i = 0; i != keys.size(); i++) + { + PGPPublicKey k = (PGPPublicKey)keys.get(i); + + k.encode(outStream); + } + } + + /** + * Returns a new key ring with the public key passed in + * either added or replacing an existing one. + * + * @param pubRing the public key ring to be modified + * @param pubKey the public key to be inserted. + * @return a new keyRing + */ + public static PGPPublicKeyRing insertPublicKey( + PGPPublicKeyRing pubRing, + PGPPublicKey pubKey) + { + List keys = new ArrayList(pubRing.keys); + boolean found = false; + boolean masterFound = false; + + for (int i = 0; i != keys.size();i++) + { + PGPPublicKey key = (PGPPublicKey)keys.get(i); + + if (key.getKeyID() == pubKey.getKeyID()) + { + found = true; + keys.set(i, pubKey); + } + if (key.isMasterKey()) + { + masterFound = true; + } + } + + if (!found) + { + if (pubKey.isMasterKey()) + { + if (masterFound) + { + throw new IllegalArgumentException("cannot add a master key to a ring that already has one"); + } + + keys.add(0, pubKey); + } + else + { + keys.add(pubKey); + } + } + + return new PGPPublicKeyRing(keys); + } + + /** + * Returns a new key ring with the public key passed in + * removed from the key ring. + * + * @param pubRing the public key ring to be modified + * @param pubKey the public key to be removed. + * @return a new keyRing, null if pubKey is not found. + */ + public static PGPPublicKeyRing removePublicKey( + PGPPublicKeyRing pubRing, + PGPPublicKey pubKey) + { + List keys = new ArrayList(pubRing.keys); + boolean found = false; + + for (int i = 0; i < keys.size();i++) + { + PGPPublicKey key = (PGPPublicKey)keys.get(i); + + if (key.getKeyID() == pubKey.getKeyID()) + { + found = true; + keys.remove(i); + } + } + + if (!found) + { + return null; + } + + return new PGPPublicKeyRing(keys); + } + + static PGPPublicKey readSubkey(BCPGInputStream in, KeyFingerPrintCalculator fingerPrintCalculator) + throws IOException, PGPException + { + PublicKeyPacket pk = (PublicKeyPacket)in.readPacket(); + TrustPacket kTrust = readOptionalTrustPacket(in); + + // PGP 8 actually leaves out the signature. + List sigList = readSignaturesAndTrust(in); + + return new PGPPublicKey(pk, kTrust, sigList, fingerPrintCalculator); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRingCollection.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRingCollection.java new file mode 100644 index 000000000..3e35c5b5b --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPPublicKeyRingCollection.java @@ -0,0 +1,369 @@ +package org.spongycastle.openpgp; + +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.util.Strings; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Often a PGP key ring file is made up of a succession of master/sub-key key rings. + * If you want to read an entire public key file in one hit this is the class for you. + */ +public class PGPPublicKeyRingCollection +{ + private Map pubRings = new HashMap(); + private List order = new ArrayList(); + + private PGPPublicKeyRingCollection( + Map pubRings, + List order) + { + this.pubRings = pubRings; + this.order = order; + } + + public PGPPublicKeyRingCollection( + byte[] encoding) + throws IOException, PGPException + { + this(new ByteArrayInputStream(encoding)); + } + + /** + * Build a PGPPublicKeyRingCollection from the passed in input stream. + * + * @param in input stream containing data + * @throws IOException if a problem parsing the base stream occurs + * @throws PGPException if an object is encountered which isn't a PGPPublicKeyRing + */ + public PGPPublicKeyRingCollection( + InputStream in) + throws IOException, PGPException + { + PGPObjectFactory pgpFact = new PGPObjectFactory(in); + Object obj; + + while ((obj = pgpFact.nextObject()) != null) + { + if (!(obj instanceof PGPPublicKeyRing)) + { + throw new PGPException(obj.getClass().getName() + " found where PGPPublicKeyRing expected"); + } + + PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)obj; + Long key = new Long(pgpPub.getPublicKey().getKeyID()); + + pubRings.put(key, pgpPub); + order.add(key); + } + } + + public PGPPublicKeyRingCollection( + Collection collection) + throws IOException, PGPException + { + Iterator it = collection.iterator(); + + while (it.hasNext()) + { + PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)it.next(); + + Long key = new Long(pgpPub.getPublicKey().getKeyID()); + + pubRings.put(key, pgpPub); + order.add(key); + } + } + + /** + * Return the number of rings in this collection. + * + * @return size of the collection + */ + public int size() + { + return order.size(); + } + + /** + * return the public key rings making up this collection. + */ + public Iterator getKeyRings() + { + return pubRings.values().iterator(); + } + + /** + * Return an iterator of the key rings associated with the passed in userID. + * + * @param userID the user ID to be matched. + * @return an iterator (possibly empty) of key rings which matched. + * @throws PGPException + */ + public Iterator getKeyRings( + String userID) + throws PGPException + { + return getKeyRings(userID, false, false); + } + + /** + * Return an iterator of the key rings associated with the passed in userID. + * <p> + * + * @param userID the user ID to be matched. + * @param matchPartial if true userID need only be a substring of an actual ID string to match. + * @return an iterator (possibly empty) of key rings which matched. + * @throws PGPException + */ + public Iterator getKeyRings( + String userID, + boolean matchPartial) + throws PGPException + { + return getKeyRings(userID, matchPartial, false); + } + + /** + * Return an iterator of the key rings associated with the passed in userID. + * <p> + * + * @param userID the user ID to be matched. + * @param matchPartial if true userID need only be a substring of an actual ID string to match. + * @param ignoreCase if true case is ignored in user ID comparisons. + * @return an iterator (possibly empty) of key rings which matched. + * @throws PGPException + */ + public Iterator getKeyRings( + String userID, + boolean matchPartial, + boolean ignoreCase) + throws PGPException + { + Iterator it = this.getKeyRings(); + List rings = new ArrayList(); + + if (ignoreCase) + { + userID = Strings.toLowerCase(userID); + } + + while (it.hasNext()) + { + PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next(); + Iterator uIt = pubRing.getPublicKey().getUserIDs(); + + while (uIt.hasNext()) + { + String next = (String)uIt.next(); + if (ignoreCase) + { + next = Strings.toLowerCase(next); + } + + if (matchPartial) + { + if (next.indexOf(userID) > -1) + { + rings.add(pubRing); + } + } + else + { + if (next.equals(userID)) + { + rings.add(pubRing); + } + } + } + } + + return rings.iterator(); + } + + /** + * Return the PGP public key associated with the given key id. + * + * @param keyID + * @return the PGP public key + * @throws PGPException + */ + public PGPPublicKey getPublicKey( + long keyID) + throws PGPException + { + Iterator it = this.getKeyRings(); + + while (it.hasNext()) + { + PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next(); + PGPPublicKey pub = pubRing.getPublicKey(keyID); + + if (pub != null) + { + return pub; + } + } + + return null; + } + + /** + * Return the public key ring which contains the key referred to by keyID. + * + * @param keyID key ID to match against + * @return the public key ring + * @throws PGPException + */ + public PGPPublicKeyRing getPublicKeyRing( + long keyID) + throws PGPException + { + Long id = new Long(keyID); + + if (pubRings.containsKey(id)) + { + return (PGPPublicKeyRing)pubRings.get(id); + } + + Iterator it = this.getKeyRings(); + + while (it.hasNext()) + { + PGPPublicKeyRing pubRing = (PGPPublicKeyRing)it.next(); + PGPPublicKey pub = pubRing.getPublicKey(keyID); + + if (pub != null) + { + return pubRing; + } + } + + return null; + } + + /** + * Return true if a key matching the passed in key ID is present, false otherwise. + * + * @param keyID key ID to look for. + * @return true if keyID present, false otherwise. + */ + public boolean contains(long keyID) + throws PGPException + { + return getPublicKey(keyID) != null; + } + + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + this.encode(bOut); + + return bOut.toByteArray(); + } + + public void encode( + OutputStream outStream) + throws IOException + { + BCPGOutputStream out; + + if (outStream instanceof BCPGOutputStream) + { + out = (BCPGOutputStream)outStream; + } + else + { + out = new BCPGOutputStream(outStream); + } + + Iterator it = order.iterator(); + while (it.hasNext()) + { + PGPPublicKeyRing sr = (PGPPublicKeyRing)pubRings.get(it.next()); + + sr.encode(out); + } + } + + + /** + * Return a new collection object containing the contents of the passed in collection and + * the passed in public key ring. + * + * @param ringCollection the collection the ring to be added to. + * @param publicKeyRing the key ring to be added. + * @return a new collection merging the current one with the passed in ring. + * @exception IllegalArgumentException if the keyID for the passed in ring is already present. + */ + public static PGPPublicKeyRingCollection addPublicKeyRing( + PGPPublicKeyRingCollection ringCollection, + PGPPublicKeyRing publicKeyRing) + { + Long key = new Long(publicKeyRing.getPublicKey().getKeyID()); + + if (ringCollection.pubRings.containsKey(key)) + { + throw new IllegalArgumentException("Collection already contains a key with a keyID for the passed in ring."); + } + + Map newPubRings = new HashMap(ringCollection.pubRings); + List newOrder = new ArrayList(ringCollection.order); + + newPubRings.put(key, publicKeyRing); + newOrder.add(key); + + return new PGPPublicKeyRingCollection(newPubRings, newOrder); + } + + /** + * Return a new collection object containing the contents of this collection with + * the passed in public key ring removed. + * + * @param ringCollection the collection the ring to be removed from. + * @param publicKeyRing the key ring to be removed. + * @return a new collection not containing the passed in ring. + * @exception IllegalArgumentException if the keyID for the passed in ring not present. + */ + public static PGPPublicKeyRingCollection removePublicKeyRing( + PGPPublicKeyRingCollection ringCollection, + PGPPublicKeyRing publicKeyRing) + { + Long key = new Long(publicKeyRing.getPublicKey().getKeyID()); + + if (!ringCollection.pubRings.containsKey(key)) + { + throw new IllegalArgumentException("Collection does not contain a key with a keyID for the passed in ring."); + } + + Map newPubRings = new HashMap(ringCollection.pubRings); + List newOrder = new ArrayList(ringCollection.order); + + newPubRings.remove(key); + + for (int i = 0; i < newOrder.size(); i++) + { + Long r = (Long)newOrder.get(i); + + if (r.longValue() == key.longValue()) + { + newOrder.remove(i); + break; + } + } + + return new PGPPublicKeyRingCollection(newPubRings, newOrder); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKey.java new file mode 100644 index 000000000..d705e5ce6 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKey.java @@ -0,0 +1,937 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.BCPGObject; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.ContainedPacket; +import org.spongycastle.bcpg.DSASecretBCPGKey; +import org.spongycastle.bcpg.ElGamalSecretBCPGKey; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSASecretBCPGKey; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SecretKeyPacket; +import org.spongycastle.bcpg.SecretSubkeyPacket; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.bcpg.UserAttributePacket; +import org.spongycastle.bcpg.UserIDPacket; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; + +/** + * general class to handle a PGP secret key object. + */ +public class PGPSecretKey +{ + SecretKeyPacket secret; + PGPPublicKey pub; + + PGPSecretKey( + SecretKeyPacket secret, + PGPPublicKey pub) + { + this.secret = secret; + this.pub = pub; + } + + PGPSecretKey( + PGPPrivateKey privKey, + PGPPublicKey pubKey, + PGPDigestCalculator checksumCalculator, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + this(privKey, pubKey, checksumCalculator, false, keyEncryptor); + } + + PGPSecretKey( + PGPPrivateKey privKey, + PGPPublicKey pubKey, + PGPDigestCalculator checksumCalculator, + boolean isMasterKey, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + this.pub = pubKey; + + BCPGObject secKey = (BCPGObject)privKey.getPrivateKeyDataPacket(); + + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut); + + pOut.writeObject(secKey); + + byte[] keyData = bOut.toByteArray(); + + pOut.write(checksum(checksumCalculator, keyData, keyData.length)); + + int encAlgorithm = keyEncryptor.getAlgorithm(); + + if (encAlgorithm != SymmetricKeyAlgorithmTags.NULL) + { + keyData = bOut.toByteArray(); // include checksum + + byte[] encData = keyEncryptor.encryptKeyData(keyData, 0, keyData.length); + byte[] iv = keyEncryptor.getCipherIV(); + + S2K s2k = keyEncryptor.getS2K(); + + int s2kUsage; + + if (checksumCalculator != null) + { + if (checksumCalculator.getAlgorithm() != HashAlgorithmTags.SHA1) + { + throw new PGPException("only SHA1 supported for key checksum calculations."); + } + s2kUsage = SecretKeyPacket.USAGE_SHA1; + } + else + { + s2kUsage = SecretKeyPacket.USAGE_CHECKSUM; + } + + if (isMasterKey) + { + this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData); + } + else + { + this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData); + } + } + else + { + if (isMasterKey) + { + this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, bOut.toByteArray()); + } + else + { + this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, bOut.toByteArray()); + } + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception encrypting key", e); + } + } + + /** + * @deprecated use method taking PBESecretKeyEncryptor + */ + public PGPSecretKey( + int certificationLevel, + PGPKeyPair keyPair, + String id, + int encAlgorithm, + char[] passPhrase, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + SecureRandom rand, + String provider) + throws PGPException, NoSuchProviderException + { + this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, false, hashedPcks, unhashedPcks, rand, provider); + } + + /** + * @deprecated use method taking PBESecretKeyEncryptor + */ + public PGPSecretKey( + int certificationLevel, + PGPKeyPair keyPair, + String id, + int encAlgorithm, + char[] passPhrase, + boolean useSHA1, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + SecureRandom rand, + String provider) + throws PGPException, NoSuchProviderException + { + this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, PGPUtil.getProvider(provider)); + } + + public PGPSecretKey( + int certificationLevel, + PGPKeyPair keyPair, + String id, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + PGPContentSignerBuilder certificationSignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + this(certificationLevel, keyPair, id, null, hashedPcks, unhashedPcks, certificationSignerBuilder, keyEncryptor); + } + + /** + * @deprecated use method taking PBESecretKeyEncryptor + */ + public PGPSecretKey( + int certificationLevel, + PGPKeyPair keyPair, + String id, + int encAlgorithm, + char[] passPhrase, + boolean useSHA1, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + SecureRandom rand, + Provider provider) + throws PGPException + { + this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, new JcaPGPContentSignerBuilder(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1).setProvider(provider)), convertSHA1Flag(useSHA1), true, new JcePBESecretKeyEncryptorBuilder(encAlgorithm, new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1)).setProvider(provider).setSecureRandom(rand).build(passPhrase)); + } + + private static PGPDigestCalculator convertSHA1Flag(boolean useSHA1) + throws PGPException + { + return useSHA1 ? new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1) : null; + } + + public PGPSecretKey( + int certificationLevel, + PGPKeyPair keyPair, + String id, + PGPDigestCalculator checksumCalculator, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + PGPContentSignerBuilder certificationSignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + this(keyPair.getPrivateKey(), certifiedPublicKey(certificationLevel, keyPair, id, hashedPcks, unhashedPcks, certificationSignerBuilder), checksumCalculator, true, keyEncryptor); + } + + private static PGPPublicKey certifiedPublicKey( + int certificationLevel, + PGPKeyPair keyPair, + String id, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + PGPContentSignerBuilder certificationSignerBuilder) + throws PGPException + { + PGPSignatureGenerator sGen; + + try + { + sGen = new PGPSignatureGenerator(certificationSignerBuilder); + } + catch (Exception e) + { + throw new PGPException("creating signature generator: " + e, e); + } + + // + // generate the certification + // + sGen.init(certificationLevel, keyPair.getPrivateKey()); + + sGen.setHashedSubpackets(hashedPcks); + sGen.setUnhashedSubpackets(unhashedPcks); + + try + { + PGPSignature certification = sGen.generateCertification(id, keyPair.getPublicKey()); + + return PGPPublicKey.addCertification(keyPair.getPublicKey(), id, certification); + } + catch (Exception e) + { + throw new PGPException("exception doing certification: " + e, e); + } + } + + /** + * @deprecated use method taking PBESecretKeyEncryptor + */ + public PGPSecretKey( + int certificationLevel, + int algorithm, + PublicKey pubKey, + PrivateKey privKey, + Date time, + String id, + int encAlgorithm, + char[] passPhrase, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + SecureRandom rand, + String provider) + throws PGPException, NoSuchProviderException + { + this(certificationLevel, new PGPKeyPair(algorithm,pubKey, privKey, time), id, encAlgorithm, passPhrase, hashedPcks, unhashedPcks, rand, provider); + } + + /** + * @deprecated use method taking PBESecretKeyEncryptor + */ + public PGPSecretKey( + int certificationLevel, + int algorithm, + PublicKey pubKey, + PrivateKey privKey, + Date time, + String id, + int encAlgorithm, + char[] passPhrase, + boolean useSHA1, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + SecureRandom rand, + String provider) + throws PGPException, NoSuchProviderException + { + this(certificationLevel, new PGPKeyPair(algorithm, pubKey, privKey, time), id, encAlgorithm, passPhrase, useSHA1, hashedPcks, unhashedPcks, rand, provider); + } + + /** + * @deprecated use method taking PGPKeyPair + */ + public PGPSecretKey( + int certificationLevel, + int algorithm, + PublicKey pubKey, + PrivateKey privKey, + Date time, + String id, + PGPDigestCalculator checksumCalculator, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + PGPContentSignerBuilder certificationSignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + this(certificationLevel, new PGPKeyPair(algorithm, pubKey, privKey, time), id, checksumCalculator, hashedPcks, unhashedPcks, certificationSignerBuilder, keyEncryptor); + } + + /** + * @deprecated use method taking PGPKeyPair + */ + public PGPSecretKey( + int certificationLevel, + int algorithm, + PublicKey pubKey, + PrivateKey privKey, + Date time, + String id, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + PGPContentSignerBuilder certificationSignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException, NoSuchProviderException + { + this(certificationLevel, new PGPKeyPair(algorithm, pubKey, privKey, time), id, null, hashedPcks, unhashedPcks, certificationSignerBuilder, keyEncryptor); + } + + /** + * Return true if this key has an algorithm type that makes it suitable to use for signing. + * <p> + * Note: with version 4 keys KeyFlags subpackets should also be considered when present for + * determining the preferred use of the key. + * + * @return true if this key algorithm is suitable for use with signing. + */ + public boolean isSigningKey() + { + int algorithm = pub.getAlgorithm(); + + return ((algorithm == PGPPublicKey.RSA_GENERAL) || (algorithm == PGPPublicKey.RSA_SIGN) + || (algorithm == PGPPublicKey.DSA) || (algorithm == PGPPublicKey.ECDSA) || (algorithm == PGPPublicKey.ELGAMAL_GENERAL)); + } + + /** + * Return true if this is a master key. + * @return true if a master key. + */ + public boolean isMasterKey() + { + return pub.isMasterKey(); + } + + /** + * Detect if the Secret Key's Private Key is empty or not + * + * @return boolean whether or not the private key is empty + */ + public boolean isPrivateKeyEmpty() + { + byte[] secKeyData = secret.getSecretKeyData(); + + return (secKeyData == null || secKeyData.length < 1); + } + + /** + * return the algorithm the key is encrypted with. + * + * @return the algorithm used to encrypt the secret key. + */ + public int getKeyEncryptionAlgorithm() + { + return secret.getEncAlgorithm(); + } + + /** + * Return the keyID of the public key associated with this key. + * + * @return the keyID associated with this key. + */ + public long getKeyID() + { + return pub.getKeyID(); + } + + /** + * Return the public key associated with this key. + * + * @return the public key for this key. + */ + public PGPPublicKey getPublicKey() + { + return pub; + } + + /** + * Return any userIDs associated with the key. + * + * @return an iterator of Strings. + */ + public Iterator getUserIDs() + { + return pub.getUserIDs(); + } + + /** + * Return any user attribute vectors associated with the key. + * + * @return an iterator of Strings. + */ + public Iterator getUserAttributes() + { + return pub.getUserAttributes(); + } + + private byte[] extractKeyData( + PBESecretKeyDecryptor decryptorFactory) + throws PGPException + { + byte[] encData = secret.getSecretKeyData(); + byte[] data = null; + + if (secret.getEncAlgorithm() != SymmetricKeyAlgorithmTags.NULL) + { + try + { + if (secret.getPublicKeyPacket().getVersion() == 4) + { + byte[] key = decryptorFactory.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K()); + + data = decryptorFactory.recoverKeyData(secret.getEncAlgorithm(), key, secret.getIV(), encData, 0, encData.length); + + boolean useSHA1 = secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1; + byte[] check = checksum(useSHA1 ? decryptorFactory.getChecksumCalculator(HashAlgorithmTags.SHA1) : null, data, (useSHA1) ? data.length - 20 : data.length - 2); + + for (int i = 0; i != check.length; i++) + { + if (check[i] != data[data.length - check.length + i]) + { + throw new PGPException("checksum mismatch at " + i + " of " + check.length); + } + } + } + else // version 2 or 3, RSA only. + { + byte[] key = decryptorFactory.makeKeyFromPassPhrase(secret.getEncAlgorithm(), secret.getS2K()); + + data = new byte[encData.length]; + + byte[] iv = new byte[secret.getIV().length]; + + System.arraycopy(secret.getIV(), 0, iv, 0, iv.length); + + // + // read in the four numbers + // + int pos = 0; + + for (int i = 0; i != 4; i++) + { + int encLen = (((encData[pos] << 8) | (encData[pos + 1] & 0xff)) + 7) / 8; + + data[pos] = encData[pos]; + data[pos + 1] = encData[pos + 1]; + + byte[] tmp = decryptorFactory.recoverKeyData(secret.getEncAlgorithm(), key, iv, encData, pos + 2, encLen); + System.arraycopy(tmp, 0, data, pos + 2, tmp.length); + pos += 2 + encLen; + + if (i != 3) + { + System.arraycopy(encData, pos - iv.length, iv, 0, iv.length); + } + } + + // + // verify and copy checksum + // + + data[pos] = encData[pos]; + data[pos + 1] = encData[pos + 1]; + + int cs = ((encData[pos] << 8) & 0xff00) | (encData[pos + 1] & 0xff); + int calcCs = 0; + for (int j = 0; j < data.length - 2; j++) + { + calcCs += data[j] & 0xff; + } + + calcCs &= 0xffff; + if (calcCs != cs) + { + throw new PGPException("checksum mismatch: passphrase wrong, expected " + + Integer.toHexString(cs) + + " found " + Integer.toHexString(calcCs)); + } + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception decrypting key", e); + } + } + else + { + data = encData; + } + + return data; + } + + /** + * Extract a PGPPrivate key from the SecretKey's encrypted contents. + * + * @param passPhrase + * @param provider + * @return PGPPrivateKey + * @throws PGPException + * @throws NoSuchProviderException + * @deprecated use method that takes a PBESecretKeyDecryptor + */ + public PGPPrivateKey extractPrivateKey( + char[] passPhrase, + String provider) + throws PGPException, NoSuchProviderException + { + return extractPrivateKey(passPhrase, PGPUtil.getProvider(provider)); + } + + /** + * Extract a PGPPrivate key from the SecretKey's encrypted contents. + * + * @param passPhrase + * @param provider + * @return PGPPrivateKey + * @throws PGPException + * @deprecated use method that takes a PBESecretKeyDecryptor + */ + public PGPPrivateKey extractPrivateKey( + char[] passPhrase, + Provider provider) + throws PGPException + { + return extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build()).setProvider(provider).build(passPhrase)); + } + + /** + * Extract a PGPPrivate key from the SecretKey's encrypted contents. + * + * @param decryptorFactory factory to use to generate a decryptor for the passed in secretKey. + * @return PGPPrivateKey the unencrypted private key. + * @throws PGPException on failure. + */ + public PGPPrivateKey extractPrivateKey( + PBESecretKeyDecryptor decryptorFactory) + throws PGPException + { + if (isPrivateKeyEmpty()) + { + return null; + } + + PublicKeyPacket pubPk = secret.getPublicKeyPacket(); + + try + { + byte[] data = extractKeyData(decryptorFactory); + BCPGInputStream in = new BCPGInputStream(new ByteArrayInputStream(data)); + + + switch (pubPk.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + case PGPPublicKey.RSA_SIGN: + RSASecretBCPGKey rsaPriv = new RSASecretBCPGKey(in); + + return new PGPPrivateKey(this.getKeyID(), pubPk, rsaPriv); + case PGPPublicKey.DSA: + DSASecretBCPGKey dsaPriv = new DSASecretBCPGKey(in); + + return new PGPPrivateKey(this.getKeyID(), pubPk, dsaPriv); + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalSecretBCPGKey elPriv = new ElGamalSecretBCPGKey(in); + + return new PGPPrivateKey(this.getKeyID(), pubPk, elPriv); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception constructing key", e); + } + } + + private static byte[] checksum(PGPDigestCalculator digCalc, byte[] bytes, int length) + throws PGPException + { + if (digCalc != null) + { + OutputStream dOut = digCalc.getOutputStream(); + + try + { + dOut.write(bytes, 0, length); + + dOut.close(); + } + catch (Exception e) + { + throw new PGPException("checksum digest calculation failed: " + e.getMessage(), e); + } + return digCalc.getDigest(); + } + else + { + int checksum = 0; + + for (int i = 0; i != length; i++) + { + checksum += bytes[i] & 0xff; + } + + byte[] check = new byte[2]; + + check[0] = (byte)(checksum >> 8); + check[1] = (byte)checksum; + + return check; + } + } + + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + this.encode(bOut); + + return bOut.toByteArray(); + } + + public void encode( + OutputStream outStream) + throws IOException + { + BCPGOutputStream out; + + if (outStream instanceof BCPGOutputStream) + { + out = (BCPGOutputStream)outStream; + } + else + { + out = new BCPGOutputStream(outStream); + } + + out.writePacket(secret); + if (pub.trustPk != null) + { + out.writePacket(pub.trustPk); + } + + if (pub.subSigs == null) // is not a sub key + { + for (int i = 0; i != pub.keySigs.size(); i++) + { + ((PGPSignature)pub.keySigs.get(i)).encode(out); + } + + for (int i = 0; i != pub.ids.size(); i++) + { + if (pub.ids.get(i) instanceof String) + { + String id = (String)pub.ids.get(i); + + out.writePacket(new UserIDPacket(id)); + } + else + { + PGPUserAttributeSubpacketVector v = (PGPUserAttributeSubpacketVector)pub.ids.get(i); + + out.writePacket(new UserAttributePacket(v.toSubpacketArray())); + } + + if (pub.idTrusts.get(i) != null) + { + out.writePacket((ContainedPacket)pub.idTrusts.get(i)); + } + + List sigs = (ArrayList)pub.idSigs.get(i); + + for (int j = 0; j != sigs.size(); j++) + { + ((PGPSignature)sigs.get(j)).encode(out); + } + } + } + else + { + for (int j = 0; j != pub.subSigs.size(); j++) + { + ((PGPSignature)pub.subSigs.get(j)).encode(out); + } + } + } + + /** + * Return a copy of the passed in secret key, encrypted using a new + * password and the passed in algorithm. + * + * @param key the PGPSecretKey to be copied. + * @param oldPassPhrase the current password for key. + * @param newPassPhrase the new password for the key. + * @param newEncAlgorithm the algorithm to be used for the encryption. + * @param rand source of randomness. + * @param provider name of the provider to use + * @deprecated use method taking PBESecretKeyDecryptor and PBESecretKeyEncryptor + */ + public static PGPSecretKey copyWithNewPassword( + PGPSecretKey key, + char[] oldPassPhrase, + char[] newPassPhrase, + int newEncAlgorithm, + SecureRandom rand, + String provider) + throws PGPException, NoSuchProviderException + { + return copyWithNewPassword(key, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand, PGPUtil.getProvider(provider)); + } + + /** + * Return a copy of the passed in secret key, encrypted using a new + * password and the passed in algorithm. + * + * @param key the PGPSecretKey to be copied. + * @param oldKeyDecryptor the current decryptor based on the current password for key. + * @param newKeyEncryptor a new encryptor based on a new password for encrypting the secret key material. + */ + public static PGPSecretKey copyWithNewPassword( + PGPSecretKey key, + PBESecretKeyDecryptor oldKeyDecryptor, + PBESecretKeyEncryptor newKeyEncryptor) + throws PGPException + { + if (key.isPrivateKeyEmpty()) + { + throw new PGPException("no private key in this SecretKey - public key present only."); + } + + byte[] rawKeyData = key.extractKeyData(oldKeyDecryptor); + int s2kUsage = key.secret.getS2KUsage(); + byte[] iv = null; + S2K s2k = null; + byte[] keyData; + int newEncAlgorithm = SymmetricKeyAlgorithmTags.NULL; + + if (newKeyEncryptor == null || newKeyEncryptor.getAlgorithm() == SymmetricKeyAlgorithmTags.NULL) + { + s2kUsage = SecretKeyPacket.USAGE_NONE; + if (key.secret.getS2KUsage() == SecretKeyPacket.USAGE_SHA1) // SHA-1 hash, need to rewrite checksum + { + keyData = new byte[rawKeyData.length - 18]; + + System.arraycopy(rawKeyData, 0, keyData, 0, keyData.length - 2); + + byte[] check = checksum(null, keyData, keyData.length - 2); + + keyData[keyData.length - 2] = check[0]; + keyData[keyData.length - 1] = check[1]; + } + else + { + keyData = rawKeyData; + } + } + else + { + if (key.secret.getPublicKeyPacket().getVersion() < 4) + { + // Version 2 or 3 - RSA Keys only + + byte[] encKey = newKeyEncryptor.getKey(); + keyData = new byte[rawKeyData.length]; + + if (newKeyEncryptor.getHashAlgorithm() != HashAlgorithmTags.MD5) + { + throw new PGPException("MD5 Digest Calculator required for version 3 key encryptor."); + } + + // + // process 4 numbers + // + int pos = 0; + for (int i = 0; i != 4; i++) + { + int encLen = (((rawKeyData[pos] << 8) | (rawKeyData[pos + 1] & 0xff)) + 7) / 8; + + keyData[pos] = rawKeyData[pos]; + keyData[pos + 1] = rawKeyData[pos + 1]; + + byte[] tmp; + if (i == 0) + { + tmp = newKeyEncryptor.encryptKeyData(encKey, rawKeyData, pos + 2, encLen); + iv = newKeyEncryptor.getCipherIV(); + + } + else + { + byte[] tmpIv = new byte[iv.length]; + + System.arraycopy(keyData, pos - iv.length, tmpIv, 0, tmpIv.length); + tmp = newKeyEncryptor.encryptKeyData(encKey, tmpIv, rawKeyData, pos + 2, encLen); + } + + System.arraycopy(tmp, 0, keyData, pos + 2, tmp.length); + pos += 2 + encLen; + } + + // + // copy in checksum. + // + keyData[pos] = rawKeyData[pos]; + keyData[pos + 1] = rawKeyData[pos + 1]; + + s2k = newKeyEncryptor.getS2K(); + newEncAlgorithm = newKeyEncryptor.getAlgorithm(); + } + else + { + keyData = newKeyEncryptor.encryptKeyData(rawKeyData, 0, rawKeyData.length); + + iv = newKeyEncryptor.getCipherIV(); + + s2k = newKeyEncryptor.getS2K(); + + newEncAlgorithm = newKeyEncryptor.getAlgorithm(); + } + } + + SecretKeyPacket secret; + if (key.secret instanceof SecretSubkeyPacket) + { + secret = new SecretSubkeyPacket(key.secret.getPublicKeyPacket(), + newEncAlgorithm, s2kUsage, s2k, iv, keyData); + } + else + { + secret = new SecretKeyPacket(key.secret.getPublicKeyPacket(), + newEncAlgorithm, s2kUsage, s2k, iv, keyData); + } + + return new PGPSecretKey(secret, key.pub); + } + + /** + * Return a copy of the passed in secret key, encrypted using a new + * password and the passed in algorithm. + * + * @param key the PGPSecretKey to be copied. + * @param oldPassPhrase the current password for key. + * @param newPassPhrase the new password for the key. + * @param newEncAlgorithm the algorithm to be used for the encryption. + * @param rand source of randomness. + * @param provider the provider to use + * @deprecated use method taking PBESecretKeyDecryptor and PBESecretKeyEncryptor + */ + public static PGPSecretKey copyWithNewPassword( + PGPSecretKey key, + char[] oldPassPhrase, + char[] newPassPhrase, + int newEncAlgorithm, + SecureRandom rand, + Provider provider) + throws PGPException + { + return copyWithNewPassword(key, new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider(provider).build()).setProvider(provider).build(oldPassPhrase), new JcePBESecretKeyEncryptorBuilder(newEncAlgorithm).setProvider(provider).setSecureRandom(rand).build(newPassPhrase)); + } + + /** + * Replace the passed the public key on the passed in secret key. + * + * @param secretKey secret key to change + * @param publicKey new public key. + * @return a new secret key. + * @throws IllegalArgumentException if keyIDs do not match. + */ + public static PGPSecretKey replacePublicKey(PGPSecretKey secretKey, PGPPublicKey publicKey) + { + if (publicKey.getKeyID() != secretKey.getKeyID()) + { + throw new IllegalArgumentException("keyIDs do not match"); + } + + return new PGPSecretKey(secretKey.secret, publicKey); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRing.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRing.java new file mode 100644 index 000000000..c182a1f1e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRing.java @@ -0,0 +1,480 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.PacketTags; +import org.spongycastle.bcpg.PublicSubkeyPacket; +import org.spongycastle.bcpg.SecretKeyPacket; +import org.spongycastle.bcpg.SecretSubkeyPacket; +import org.spongycastle.bcpg.TrustPacket; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; + +/** + * Class to hold a single master secret key and its subkeys. + * <p> + * Often PGP keyring files consist of multiple master keys, if you are trying to process + * or construct one of these you should use the PGPSecretKeyRingCollection class. + */ +public class PGPSecretKeyRing + extends PGPKeyRing +{ + List keys; + List extraPubKeys; + + PGPSecretKeyRing(List keys) + { + this(keys, new ArrayList()); + } + + private PGPSecretKeyRing(List keys, List extraPubKeys) + { + this.keys = keys; + this.extraPubKeys = extraPubKeys; + } + + /** + * @deprecated use version that takes KeyFingerprintCalculator + */ + public PGPSecretKeyRing( + byte[] encoding) + throws IOException, PGPException + { + this(new ByteArrayInputStream(encoding)); + } + + public PGPSecretKeyRing( + byte[] encoding, + KeyFingerPrintCalculator fingerPrintCalculator) + throws IOException, PGPException + { + this(new ByteArrayInputStream(encoding), fingerPrintCalculator); + } + + /** + * @deprecated use version that takes KeyFingerprintCalculator + */ + public PGPSecretKeyRing( + InputStream in) + throws IOException, PGPException + { + this(in, new JcaKeyFingerprintCalculator()); + } + + public PGPSecretKeyRing( + InputStream in, + KeyFingerPrintCalculator fingerPrintCalculator) + throws IOException, PGPException + { + this.keys = new ArrayList(); + this.extraPubKeys = new ArrayList(); + + BCPGInputStream pIn = wrap(in); + + int initialTag = pIn.nextPacketTag(); + if (initialTag != PacketTags.SECRET_KEY && initialTag != PacketTags.SECRET_SUBKEY) + { + throw new IOException( + "secret key ring doesn't start with secret key tag: " + + "tag 0x" + Integer.toHexString(initialTag)); + } + + SecretKeyPacket secret = (SecretKeyPacket)pIn.readPacket(); + + // + // ignore GPG comment packets if found. + // + while (pIn.nextPacketTag() == PacketTags.EXPERIMENTAL_2) + { + pIn.readPacket(); + } + + TrustPacket trust = readOptionalTrustPacket(pIn); + + // revocation and direct signatures + List keySigs = readSignaturesAndTrust(pIn); + + List ids = new ArrayList(); + List idTrusts = new ArrayList(); + List idSigs = new ArrayList(); + readUserIDs(pIn, ids, idTrusts, idSigs); + + keys.add(new PGPSecretKey(secret, new PGPPublicKey(secret.getPublicKeyPacket(), trust, keySigs, ids, idTrusts, idSigs, fingerPrintCalculator))); + + + // Read subkeys + while (pIn.nextPacketTag() == PacketTags.SECRET_SUBKEY + || pIn.nextPacketTag() == PacketTags.PUBLIC_SUBKEY) + { + if (pIn.nextPacketTag() == PacketTags.SECRET_SUBKEY) + { + SecretSubkeyPacket sub = (SecretSubkeyPacket)pIn.readPacket(); + + // + // ignore GPG comment packets if found. + // + while (pIn.nextPacketTag() == PacketTags.EXPERIMENTAL_2) + { + pIn.readPacket(); + } + + TrustPacket subTrust = readOptionalTrustPacket(pIn); + List sigList = readSignaturesAndTrust(pIn); + + keys.add(new PGPSecretKey(sub, new PGPPublicKey(sub.getPublicKeyPacket(), subTrust, sigList, fingerPrintCalculator))); + } + else + { + PublicSubkeyPacket sub = (PublicSubkeyPacket)pIn.readPacket(); + + TrustPacket subTrust = readOptionalTrustPacket(pIn); + List sigList = readSignaturesAndTrust(pIn); + + extraPubKeys.add(new PGPPublicKey(sub, subTrust, sigList, fingerPrintCalculator)); + } + } + } + + /** + * Return the public key for the master key. + * + * @return PGPPublicKey + */ + public PGPPublicKey getPublicKey() + { + return ((PGPSecretKey)keys.get(0)).getPublicKey(); + } + + /** + * Return the public key referred to by the passed in keyID if it + * is present. + * + * @param keyID + * @return PGPPublicKey + */ + public PGPPublicKey getPublicKey( + long keyID) + { + PGPSecretKey key = getSecretKey(keyID); + if (key != null) + { + return key.getPublicKey(); + } + + for (int i = 0; i != extraPubKeys.size(); i++) + { + PGPPublicKey k = (PGPPublicKey)keys.get(i); + + if (keyID == k.getKeyID()) + { + return k; + } + } + + return null; + } + + /** + * Return an iterator containing all the public keys. + * + * @return Iterator + */ + public Iterator getPublicKeys() + { + List pubKeys = new ArrayList(); + + for (Iterator it = getSecretKeys(); it.hasNext();) + { + pubKeys.add(((PGPSecretKey)it.next()).getPublicKey()); + } + + pubKeys.addAll(extraPubKeys); + + return Collections.unmodifiableList(pubKeys).iterator(); + } + + /** + * Return the master private key. + * + * @return PGPSecretKey + */ + public PGPSecretKey getSecretKey() + { + return ((PGPSecretKey)keys.get(0)); + } + + /** + * Return an iterator containing all the secret keys. + * + * @return Iterator + */ + public Iterator getSecretKeys() + { + return Collections.unmodifiableList(keys).iterator(); + } + + public PGPSecretKey getSecretKey( + long keyId) + { + for (int i = 0; i != keys.size(); i++) + { + PGPSecretKey k = (PGPSecretKey)keys.get(i); + + if (keyId == k.getKeyID()) + { + return k; + } + } + + return null; + } + + /** + * Return an iterator of the public keys in the secret key ring that + * have no matching private key. At the moment only personal certificate data + * appears in this fashion. + * + * @return iterator of unattached, or extra, public keys. + */ + public Iterator getExtraPublicKeys() + { + return extraPubKeys.iterator(); + } + + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + this.encode(bOut); + + return bOut.toByteArray(); + } + + public void encode( + OutputStream outStream) + throws IOException + { + for (int i = 0; i != keys.size(); i++) + { + PGPSecretKey k = (PGPSecretKey)keys.get(i); + + k.encode(outStream); + } + for (int i = 0; i != extraPubKeys.size(); i++) + { + PGPPublicKey k = (PGPPublicKey)extraPubKeys.get(i); + + k.encode(outStream); + } + } + + /** + * Replace the public key set on the secret ring with the corresponding key off the public ring. + * + * @param secretRing secret ring to be changed. + * @param publicRing public ring containing the new public key set. + */ + public static PGPSecretKeyRing replacePublicKeys(PGPSecretKeyRing secretRing, PGPPublicKeyRing publicRing) + { + List newList = new ArrayList(secretRing.keys.size()); + + for (Iterator it = secretRing.keys.iterator(); it.hasNext();) + { + PGPSecretKey sk = (PGPSecretKey)it.next(); + PGPPublicKey pk = publicRing.getPublicKey(sk.getKeyID()); + + newList.add(PGPSecretKey.replacePublicKey(sk, pk)); + } + + return new PGPSecretKeyRing(newList); + } + + /** + * Return a copy of the passed in secret key ring, with the master key and sub keys encrypted + * using a new password and the passed in algorithm. + * + * @param ring the PGPSecretKeyRing to be copied. + * @param oldPassPhrase the current password for key. + * @param newPassPhrase the new password for the key. + * @param newEncAlgorithm the algorithm to be used for the encryption. + * @param rand source of randomness. + * @param provider name of the provider to use + * @deprecated use version taking PBESecretKeyEncryptor/PBESecretKeyDecryptor + */ + public static PGPSecretKeyRing copyWithNewPassword( + PGPSecretKeyRing ring, + char[] oldPassPhrase, + char[] newPassPhrase, + int newEncAlgorithm, + SecureRandom rand, + String provider) + throws PGPException, NoSuchProviderException + { + return copyWithNewPassword(ring, oldPassPhrase, newPassPhrase, newEncAlgorithm, rand, PGPUtil.getProvider(provider)); + } + + /** + * Return a copy of the passed in secret key ring, with the master key and sub keys encrypted + * using a new password and the passed in algorithm. + * + * @param ring the PGPSecretKeyRing to be copied. + * @param oldPassPhrase the current password for key. + * @param newPassPhrase the new password for the key. + * @param newEncAlgorithm the algorithm to be used for the encryption. + * @param rand source of randomness. + * @param provider provider to use + * @deprecated use version taking PBESecretKeyEncryptor/PBESecretKeyDecryptor + */ + public static PGPSecretKeyRing copyWithNewPassword( + PGPSecretKeyRing ring, + char[] oldPassPhrase, + char[] newPassPhrase, + int newEncAlgorithm, + SecureRandom rand, + Provider provider) + throws PGPException + { + List newKeys = new ArrayList(ring.keys.size()); + + for (Iterator keys = ring.getSecretKeys(); keys.hasNext();) + { + newKeys.add(PGPSecretKey.copyWithNewPassword((PGPSecretKey)keys.next(), oldPassPhrase, newPassPhrase, newEncAlgorithm, rand, provider)); + } + + return new PGPSecretKeyRing(newKeys, ring.extraPubKeys); + } + + /** + * Return a copy of the passed in secret key ring, with the private keys (where present) associated with the master key and sub keys + * are encrypted using a new password and the passed in algorithm. + * + * @param ring the PGPSecretKeyRing to be copied. + * @param oldKeyDecryptor the current decryptor based on the current password for key. + * @param newKeyEncryptor a new encryptor based on a new password for encrypting the secret key material. + * @return the updated key ring. + */ + public static PGPSecretKeyRing copyWithNewPassword( + PGPSecretKeyRing ring, + PBESecretKeyDecryptor oldKeyDecryptor, + PBESecretKeyEncryptor newKeyEncryptor) + throws PGPException + { + List newKeys = new ArrayList(ring.keys.size()); + + for (Iterator keys = ring.getSecretKeys(); keys.hasNext();) + { + PGPSecretKey key = (PGPSecretKey)keys.next(); + + if (key.isPrivateKeyEmpty()) + { + newKeys.add(key); + } + else + { + newKeys.add(PGPSecretKey.copyWithNewPassword(key, oldKeyDecryptor, newKeyEncryptor)); + } + } + + return new PGPSecretKeyRing(newKeys, ring.extraPubKeys); + } + + /** + * Returns a new key ring with the secret key passed in either added or + * replacing an existing one with the same key ID. + * + * @param secRing the secret key ring to be modified. + * @param secKey the secret key to be added. + * @return a new secret key ring. + */ + public static PGPSecretKeyRing insertSecretKey( + PGPSecretKeyRing secRing, + PGPSecretKey secKey) + { + List keys = new ArrayList(secRing.keys); + boolean found = false; + boolean masterFound = false; + + for (int i = 0; i != keys.size();i++) + { + PGPSecretKey key = (PGPSecretKey)keys.get(i); + + if (key.getKeyID() == secKey.getKeyID()) + { + found = true; + keys.set(i, secKey); + } + if (key.isMasterKey()) + { + masterFound = true; + } + } + + if (!found) + { + if (secKey.isMasterKey()) + { + if (masterFound) + { + throw new IllegalArgumentException("cannot add a master key to a ring that already has one"); + } + + keys.add(0, secKey); + } + else + { + keys.add(secKey); + } + } + + return new PGPSecretKeyRing(keys, secRing.extraPubKeys); + } + + /** + * Returns a new key ring with the secret key passed in removed from the + * key ring. + * + * @param secRing the secret key ring to be modified. + * @param secKey the secret key to be removed. + * @return a new secret key ring, or null if secKey is not found. + */ + public static PGPSecretKeyRing removeSecretKey( + PGPSecretKeyRing secRing, + PGPSecretKey secKey) + { + List keys = new ArrayList(secRing.keys); + boolean found = false; + + for (int i = 0; i < keys.size();i++) + { + PGPSecretKey key = (PGPSecretKey)keys.get(i); + + if (key.getKeyID() == secKey.getKeyID()) + { + found = true; + keys.remove(i); + } + } + + if (!found) + { + return null; + } + + return new PGPSecretKeyRing(keys, secRing.extraPubKeys); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRingCollection.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRingCollection.java new file mode 100644 index 000000000..a8f09fc5d --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSecretKeyRingCollection.java @@ -0,0 +1,367 @@ +package org.spongycastle.openpgp; + +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.util.Strings; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Often a PGP key ring file is made up of a succession of master/sub-key key rings. + * If you want to read an entire secret key file in one hit this is the class for you. + */ +public class PGPSecretKeyRingCollection +{ + private Map secretRings = new HashMap(); + private List order = new ArrayList(); + + private PGPSecretKeyRingCollection( + Map secretRings, + List order) + { + this.secretRings = secretRings; + this.order = order; + } + + public PGPSecretKeyRingCollection( + byte[] encoding) + throws IOException, PGPException + { + this(new ByteArrayInputStream(encoding)); + } + + /** + * Build a PGPSecretKeyRingCollection from the passed in input stream. + * + * @param in input stream containing data + * @throws IOException if a problem parsinh the base stream occurs + * @throws PGPException if an object is encountered which isn't a PGPSecretKeyRing + */ + public PGPSecretKeyRingCollection( + InputStream in) + throws IOException, PGPException + { + PGPObjectFactory pgpFact = new PGPObjectFactory(in); + Object obj; + + while ((obj = pgpFact.nextObject()) != null) + { + if (!(obj instanceof PGPSecretKeyRing)) + { + throw new PGPException(obj.getClass().getName() + " found where PGPSecretKeyRing expected"); + } + + PGPSecretKeyRing pgpSecret = (PGPSecretKeyRing)obj; + Long key = new Long(pgpSecret.getPublicKey().getKeyID()); + + secretRings.put(key, pgpSecret); + order.add(key); + } + } + + public PGPSecretKeyRingCollection( + Collection collection) + throws IOException, PGPException + { + Iterator it = collection.iterator(); + + while (it.hasNext()) + { + PGPSecretKeyRing pgpSecret = (PGPSecretKeyRing)it.next(); + Long key = new Long(pgpSecret.getPublicKey().getKeyID()); + + secretRings.put(key, pgpSecret); + order.add(key); + } + } + + /** + * Return the number of rings in this collection. + * + * @return size of the collection + */ + public int size() + { + return order.size(); + } + + /** + * return the secret key rings making up this collection. + */ + public Iterator getKeyRings() + { + return secretRings.values().iterator(); + } + + /** + * Return an iterator of the key rings associated with the passed in userID. + * + * @param userID the user ID to be matched. + * @return an iterator (possibly empty) of key rings which matched. + * @throws PGPException + */ + public Iterator getKeyRings( + String userID) + throws PGPException + { + return getKeyRings(userID, false, false); + } + + /** + * Return an iterator of the key rings associated with the passed in userID. + * <p> + * + * @param userID the user ID to be matched. + * @param matchPartial if true userID need only be a substring of an actual ID string to match. + * @return an iterator (possibly empty) of key rings which matched. + * @throws PGPException + */ + public Iterator getKeyRings( + String userID, + boolean matchPartial) + throws PGPException + { + return getKeyRings(userID, matchPartial, false); + } + + /** + * Return an iterator of the key rings associated with the passed in userID. + * <p> + * + * @param userID the user ID to be matched. + * @param matchPartial if true userID need only be a substring of an actual ID string to match. + * @param ignoreCase if true case is ignored in user ID comparisons. + * @return an iterator (possibly empty) of key rings which matched. + * @throws PGPException + */ + public Iterator getKeyRings( + String userID, + boolean matchPartial, + boolean ignoreCase) + throws PGPException + { + Iterator it = this.getKeyRings(); + List rings = new ArrayList(); + + if (ignoreCase) + { + userID = Strings.toLowerCase(userID); + } + + while (it.hasNext()) + { + PGPSecretKeyRing secRing = (PGPSecretKeyRing)it.next(); + Iterator uIt = secRing.getSecretKey().getUserIDs(); + + while (uIt.hasNext()) + { + String next = (String)uIt.next(); + if (ignoreCase) + { + next = Strings.toLowerCase(next); + } + + if (matchPartial) + { + if (next.indexOf(userID) > -1) + { + rings.add(secRing); + } + } + else + { + if (next.equals(userID)) + { + rings.add(secRing); + } + } + } + } + + return rings.iterator(); + } + + /** + * Return the PGP secret key associated with the given key id. + * + * @param keyID + * @return the secret key + * @throws PGPException + */ + public PGPSecretKey getSecretKey( + long keyID) + throws PGPException + { + Iterator it = this.getKeyRings(); + + while (it.hasNext()) + { + PGPSecretKeyRing secRing = (PGPSecretKeyRing)it.next(); + PGPSecretKey sec = secRing.getSecretKey(keyID); + + if (sec != null) + { + return sec; + } + } + + return null; + } + + /** + * Return the secret key ring which contains the key referred to by keyID. + * + * @param keyID + * @return the secret key ring + * @throws PGPException + */ + public PGPSecretKeyRing getSecretKeyRing( + long keyID) + throws PGPException + { + Long id = new Long(keyID); + + if (secretRings.containsKey(id)) + { + return (PGPSecretKeyRing)secretRings.get(id); + } + + Iterator it = this.getKeyRings(); + + while (it.hasNext()) + { + PGPSecretKeyRing secretRing = (PGPSecretKeyRing)it.next(); + PGPSecretKey secret = secretRing.getSecretKey(keyID); + + if (secret != null) + { + return secretRing; + } + } + + return null; + } + + /** + * Return true if a key matching the passed in key ID is present, false otherwise. + * + * @param keyID key ID to look for. + * @return true if keyID present, false otherwise. + */ + public boolean contains(long keyID) + throws PGPException + { + return getSecretKey(keyID) != null; + } + + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + this.encode(bOut); + + return bOut.toByteArray(); + } + + public void encode( + OutputStream outStream) + throws IOException + { + BCPGOutputStream out; + + if (outStream instanceof BCPGOutputStream) + { + out = (BCPGOutputStream)outStream; + } + else + { + out = new BCPGOutputStream(outStream); + } + + Iterator it = order.iterator(); + while (it.hasNext()) + { + PGPSecretKeyRing sr = (PGPSecretKeyRing)secretRings.get(it.next()); + + sr.encode(out); + } + } + + /** + * Return a new collection object containing the contents of the passed in collection and + * the passed in secret key ring. + * + * @param ringCollection the collection the ring to be added to. + * @param secretKeyRing the key ring to be added. + * @return a new collection merging the current one with the passed in ring. + * @exception IllegalArgumentException if the keyID for the passed in ring is already present. + */ + public static PGPSecretKeyRingCollection addSecretKeyRing( + PGPSecretKeyRingCollection ringCollection, + PGPSecretKeyRing secretKeyRing) + { + Long key = new Long(secretKeyRing.getPublicKey().getKeyID()); + + if (ringCollection.secretRings.containsKey(key)) + { + throw new IllegalArgumentException("Collection already contains a key with a keyID for the passed in ring."); + } + + Map newSecretRings = new HashMap(ringCollection.secretRings); + List newOrder = new ArrayList(ringCollection.order); + + newSecretRings.put(key, secretKeyRing); + newOrder.add(key); + + return new PGPSecretKeyRingCollection(newSecretRings, newOrder); + } + + /** + * Return a new collection object containing the contents of this collection with + * the passed in secret key ring removed. + * + * @param ringCollection the collection the ring to be removed from. + * @param secretKeyRing the key ring to be removed. + * @return a new collection merging the current one with the passed in ring. + * @exception IllegalArgumentException if the keyID for the passed in ring is not present. + */ + public static PGPSecretKeyRingCollection removeSecretKeyRing( + PGPSecretKeyRingCollection ringCollection, + PGPSecretKeyRing secretKeyRing) + { + Long key = new Long(secretKeyRing.getPublicKey().getKeyID()); + + if (!ringCollection.secretRings.containsKey(key)) + { + throw new IllegalArgumentException("Collection does not contain a key with a keyID for the passed in ring."); + } + + Map newSecretRings = new HashMap(ringCollection.secretRings); + List newOrder = new ArrayList(ringCollection.order); + + newSecretRings.remove(key); + + for (int i = 0; i < newOrder.size(); i++) + { + Long r = (Long)newOrder.get(i); + + if (r.longValue() == key.longValue()) + { + newOrder.remove(i); + break; + } + } + + return new PGPSecretKeyRingCollection(newSecretRings, newOrder); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignature.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignature.java new file mode 100644 index 000000000..19a041fe5 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignature.java @@ -0,0 +1,564 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SignatureException; +import java.util.Date; + +import org.spongycastle.asn1.ASN1EncodableVector; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DERSequence; +import org.spongycastle.bcpg.BCPGInputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.SignaturePacket; +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.TrustPacket; +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.openpgp.operator.PGPContentVerifier; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.spongycastle.util.BigIntegers; +import org.spongycastle.util.Strings; + +/** + *A PGP signature object. + */ +public class PGPSignature +{ + public static final int BINARY_DOCUMENT = 0x00; + public static final int CANONICAL_TEXT_DOCUMENT = 0x01; + public static final int STAND_ALONE = 0x02; + + public static final int DEFAULT_CERTIFICATION = 0x10; + public static final int NO_CERTIFICATION = 0x11; + public static final int CASUAL_CERTIFICATION = 0x12; + public static final int POSITIVE_CERTIFICATION = 0x13; + + public static final int SUBKEY_BINDING = 0x18; + public static final int PRIMARYKEY_BINDING = 0x19; + public static final int DIRECT_KEY = 0x1f; + public static final int KEY_REVOCATION = 0x20; + public static final int SUBKEY_REVOCATION = 0x28; + public static final int CERTIFICATION_REVOCATION = 0x30; + public static final int TIMESTAMP = 0x40; + + private SignaturePacket sigPck; + private int signatureType; + private TrustPacket trustPck; + private PGPContentVerifier verifier; + private byte lastb; + private OutputStream sigOut; + + PGPSignature( + BCPGInputStream pIn) + throws IOException, PGPException + { + this((SignaturePacket)pIn.readPacket()); + } + + PGPSignature( + SignaturePacket sigPacket) + throws PGPException + { + sigPck = sigPacket; + signatureType = sigPck.getSignatureType(); + trustPck = null; + } + + PGPSignature( + SignaturePacket sigPacket, + TrustPacket trustPacket) + throws PGPException + { + this(sigPacket); + + this.trustPck = trustPacket; + } + + /** + * Return the OpenPGP version number for this signature. + * + * @return signature version number. + */ + public int getVersion() + { + return sigPck.getVersion(); + } + + /** + * Return the key algorithm associated with this signature. + * @return signature key algorithm. + */ + public int getKeyAlgorithm() + { + return sigPck.getKeyAlgorithm(); + } + + /** + * Return the hash algorithm associated with this signature. + * @return signature hash algorithm. + */ + public int getHashAlgorithm() + { + return sigPck.getHashAlgorithm(); + } + + /** + * @deprecated use init(PGPContentVerifierBuilderProvider, PGPPublicKey) + */ + public void initVerify( + PGPPublicKey pubKey, + String provider) + throws NoSuchProviderException, PGPException + { + initVerify(pubKey, PGPUtil.getProvider(provider)); + } + + /** + * @deprecated use init(PGPContentVerifierBuilderProvider, PGPPublicKey) + */ + public void initVerify( + PGPPublicKey pubKey, + Provider provider) + throws PGPException + { + init(new JcaPGPContentVerifierBuilderProvider().setProvider(provider), pubKey); + } + + public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPPublicKey pubKey) + throws PGPException + { + PGPContentVerifierBuilder verifierBuilder = verifierBuilderProvider.get(sigPck.getKeyAlgorithm(), sigPck.getHashAlgorithm()); + + verifier = verifierBuilder.build(pubKey); + + lastb = 0; + sigOut = verifier.getOutputStream(); + } + + public void update( + byte b) + throws SignatureException + { + if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + if (b == '\r') + { + byteUpdate((byte)'\r'); + byteUpdate((byte)'\n'); + } + else if (b == '\n') + { + if (lastb != '\r') + { + byteUpdate((byte)'\r'); + byteUpdate((byte)'\n'); + } + } + else + { + byteUpdate(b); + } + + lastb = b; + } + else + { + byteUpdate(b); + } + } + + public void update( + byte[] bytes) + throws SignatureException + { + this.update(bytes, 0, bytes.length); + } + + public void update( + byte[] bytes, + int off, + int length) + throws SignatureException + { + if (signatureType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + int finish = off + length; + + for (int i = off; i != finish; i++) + { + this.update(bytes[i]); + } + } + else + { + blockUpdate(bytes, off, length); + } + } + + private void byteUpdate(byte b) + throws SignatureException + { + try + { + sigOut.write(b); + } + catch (IOException e) + { // TODO: we really should get rid of signature exception next.... + throw new SignatureException(e.getMessage()); + } + } + + private void blockUpdate(byte[] block, int off, int len) + throws SignatureException + { + try + { + sigOut.write(block, off, len); + } + catch (IOException e) + { + throw new IllegalStateException(e.getMessage()); + } + } + + public boolean verify() + throws PGPException, SignatureException + { + try + { + sigOut.write(this.getSignatureTrailer()); + + sigOut.close(); + } + catch (IOException e) + { + throw new SignatureException(e.getMessage()); + } + + return verifier.verify(this.getSignature()); + } + + + private void updateWithIdData(int header, byte[] idBytes) + throws SignatureException + { + this.update((byte)header); + this.update((byte)(idBytes.length >> 24)); + this.update((byte)(idBytes.length >> 16)); + this.update((byte)(idBytes.length >> 8)); + this.update((byte)(idBytes.length)); + this.update(idBytes); + } + + private void updateWithPublicKey(PGPPublicKey key) + throws PGPException, SignatureException + { + byte[] keyBytes = getEncodedPublicKey(key); + + this.update((byte)0x99); + this.update((byte)(keyBytes.length >> 8)); + this.update((byte)(keyBytes.length)); + this.update(keyBytes); + } + + /** + * Verify the signature as certifying the passed in public key as associated + * with the passed in user attributes. + * + * @param userAttributes user attributes the key was stored under + * @param key the key to be verified. + * @return true if the signature matches, false otherwise. + * @throws PGPException + * @throws SignatureException + */ + public boolean verifyCertification( + PGPUserAttributeSubpacketVector userAttributes, + PGPPublicKey key) + throws PGPException, SignatureException + { + if (verifier == null) + { + throw new PGPException("PGPSignature not initialised - call init()."); + } + + updateWithPublicKey(key); + + // + // hash in the userAttributes + // + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray(); + for (int i = 0; i != packets.length; i++) + { + packets[i].encode(bOut); + } + updateWithIdData(0xd1, bOut.toByteArray()); + } + catch (IOException e) + { + throw new PGPException("cannot encode subpacket array", e); + } + + addTrailer(); + + return verifier.verify(this.getSignature()); + } + + /** + * Verify the signature as certifying the passed in public key as associated + * with the passed in id. + * + * @param id id the key was stored under + * @param key the key to be verified. + * @return true if the signature matches, false otherwise. + * @throws PGPException + * @throws SignatureException + */ + public boolean verifyCertification( + String id, + PGPPublicKey key) + throws PGPException, SignatureException + { + if (verifier == null) + { + throw new PGPException("PGPSignature not initialised - call init()."); + } + + updateWithPublicKey(key); + + // + // hash in the id + // + updateWithIdData(0xb4, Strings.toUTF8ByteArray(id)); + + addTrailer(); + + return verifier.verify(this.getSignature()); + } + + /** + * Verify a certification for the passed in key against the passed in + * master key. + * + * @param masterKey the key we are verifying against. + * @param pubKey the key we are verifying. + * @return true if the certification is valid, false otherwise. + * @throws SignatureException + * @throws PGPException + */ + public boolean verifyCertification( + PGPPublicKey masterKey, + PGPPublicKey pubKey) + throws SignatureException, PGPException + { + if (verifier == null) + { + throw new PGPException("PGPSignature not initialised - call init()."); + } + + updateWithPublicKey(masterKey); + updateWithPublicKey(pubKey); + + addTrailer(); + + return verifier.verify(this.getSignature()); + } + + private void addTrailer() + throws SignatureException + { + try + { + sigOut.write(sigPck.getSignatureTrailer()); + + sigOut.close(); + } + catch (IOException e) + { + throw new SignatureException(e.getMessage()); + } + } + + /** + * Verify a key certification, such as a revocation, for the passed in key. + * + * @param pubKey the key we are checking. + * @return true if the certification is valid, false otherwise. + * @throws SignatureException + * @throws PGPException + */ + public boolean verifyCertification( + PGPPublicKey pubKey) + throws SignatureException, PGPException + { + if (verifier == null) + { + throw new PGPException("PGPSignature not initialised - call init()."); + } + + if (this.getSignatureType() != KEY_REVOCATION + && this.getSignatureType() != SUBKEY_REVOCATION) + { + throw new PGPException("signature is not a key signature"); + } + + updateWithPublicKey(pubKey); + + addTrailer(); + + return verifier.verify(this.getSignature()); + } + + public int getSignatureType() + { + return sigPck.getSignatureType(); + } + + /** + * Return the id of the key that created the signature. + * @return keyID of the signatures corresponding key. + */ + public long getKeyID() + { + return sigPck.getKeyID(); + } + + /** + * Return the creation time of the signature. + * + * @return the signature creation time. + */ + public Date getCreationTime() + { + return new Date(sigPck.getCreationTime()); + } + + public byte[] getSignatureTrailer() + { + return sigPck.getSignatureTrailer(); + } + + /** + * Return true if the signature has either hashed or unhashed subpackets. + * + * @return true if either hashed or unhashed subpackets are present, false otherwise. + */ + public boolean hasSubpackets() + { + return sigPck.getHashedSubPackets() != null || sigPck.getUnhashedSubPackets() != null; + } + + public PGPSignatureSubpacketVector getHashedSubPackets() + { + return createSubpacketVector(sigPck.getHashedSubPackets()); + } + + public PGPSignatureSubpacketVector getUnhashedSubPackets() + { + return createSubpacketVector(sigPck.getUnhashedSubPackets()); + } + + private PGPSignatureSubpacketVector createSubpacketVector(SignatureSubpacket[] pcks) + { + if (pcks != null) + { + return new PGPSignatureSubpacketVector(pcks); + } + + return null; + } + + public byte[] getSignature() + throws PGPException + { + MPInteger[] sigValues = sigPck.getSignature(); + byte[] signature; + + if (sigValues != null) + { + if (sigValues.length == 1) // an RSA signature + { + signature = BigIntegers.asUnsignedByteArray(sigValues[0].getValue()); + } + else + { + try + { + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(sigValues[0].getValue())); + v.add(new ASN1Integer(sigValues[1].getValue())); + + signature = new DERSequence(v).getEncoded(); + } + catch (IOException e) + { + throw new PGPException("exception encoding DSA sig.", e); + } + } + } + else + { + signature = sigPck.getSignatureBytes(); + } + + return signature; + } + + public byte[] getEncoded() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + this.encode(bOut); + + return bOut.toByteArray(); + } + + public void encode( + OutputStream outStream) + throws IOException + { + BCPGOutputStream out; + + if (outStream instanceof BCPGOutputStream) + { + out = (BCPGOutputStream)outStream; + } + else + { + out = new BCPGOutputStream(outStream); + } + + out.writePacket(sigPck); + if (trustPck != null) + { + out.writePacket(trustPck); + } + } + + private byte[] getEncodedPublicKey( + PGPPublicKey pubKey) + throws PGPException + { + byte[] keyBytes; + + try + { + keyBytes = pubKey.publicPk.getEncodedContents(); + } + catch (IOException e) + { + throw new PGPException("exception preparing key.", e); + } + + return keyBytes; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureGenerator.java new file mode 100644 index 000000000..6383f0a8c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureGenerator.java @@ -0,0 +1,579 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.util.Date; + +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.OnePassSignaturePacket; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SignaturePacket; +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.bcpg.sig.IssuerKeyID; +import org.spongycastle.bcpg.sig.SignatureCreationTime; +import org.spongycastle.openpgp.operator.PGPContentSigner; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.util.Strings; + +/** + * Generator for PGP Signatures. + */ +public class PGPSignatureGenerator +{ + private SignatureSubpacket[] unhashed = new SignatureSubpacket[0]; + private SignatureSubpacket[] hashed = new SignatureSubpacket[0]; + private OutputStream sigOut; + private PGPContentSignerBuilder contentSignerBuilder; + private PGPContentSigner contentSigner; + private int sigType; + private byte lastb; + private int providedKeyAlgorithm = -1; + + /** + * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. + * + * @param keyAlgorithm keyAlgorithm to use for signing + * @param hashAlgorithm algorithm to use for digest + * @param provider provider to use for digest algorithm + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws PGPException + * @deprecated use method taking a PGPContentSignerBuilder + */ + public PGPSignatureGenerator( + int keyAlgorithm, + int hashAlgorithm, + String provider) + throws NoSuchAlgorithmException, NoSuchProviderException, PGPException + { + this(keyAlgorithm, provider, hashAlgorithm, provider); + } + + /** + * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. + * + * @deprecated use method taking a PGPContentSignerBuilder + */ + public PGPSignatureGenerator( + int keyAlgorithm, + int hashAlgorithm, + Provider provider) + throws NoSuchAlgorithmException, PGPException + { + this(keyAlgorithm, provider, hashAlgorithm, provider); + } + + /** + * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. + * + * @param keyAlgorithm keyAlgorithm to use for signing + * @param sigProvider provider to use for signature generation + * @param hashAlgorithm algorithm to use for digest + * @param digProvider provider to use for digest algorithm + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws PGPException + * @deprecated use method taking a PGPContentSignerBuilder + */ + public PGPSignatureGenerator( + int keyAlgorithm, + String sigProvider, + int hashAlgorithm, + String digProvider) + throws NoSuchAlgorithmException, NoSuchProviderException, PGPException + { + this(keyAlgorithm, PGPUtil.getProvider(sigProvider), hashAlgorithm, PGPUtil.getProvider(digProvider)); + } + + /** + * + * @param keyAlgorithm + * @param sigProvider + * @param hashAlgorithm + * @param digProvider + * @throws NoSuchAlgorithmException + * @throws PGPException + * @deprecated use constructor taking PGPContentSignerBuilder. + */ + public PGPSignatureGenerator( + int keyAlgorithm, + Provider sigProvider, + int hashAlgorithm, + Provider digProvider) + throws NoSuchAlgorithmException, PGPException + { + this.providedKeyAlgorithm = keyAlgorithm; + this.contentSignerBuilder = new JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm).setProvider(sigProvider).setDigestProvider(digProvider); + } + + /** + * Create a signature generator built on the passed in contentSignerBuilder. + * + * @param contentSignerBuilder builder to produce PGPContentSigner objects for generating signatures. + */ + public PGPSignatureGenerator( + PGPContentSignerBuilder contentSignerBuilder) + { + this.contentSignerBuilder = contentSignerBuilder; + } + + /** + * Initialise the generator for signing. + * + * @param signatureType + * @param key + * @throws PGPException + * @deprecated use init() method + */ + public void initSign( + int signatureType, + PGPPrivateKey key) + throws PGPException + { + contentSigner = contentSignerBuilder.build(signatureType, key); + sigOut = contentSigner.getOutputStream(); + sigType = contentSigner.getType(); + lastb = 0; + + if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm()) + { + throw new PGPException("key algorithm mismatch"); + } + } + + /** + * Initialise the generator for signing. + * + * @param signatureType + * @param key + * @throws PGPException + */ + public void init( + int signatureType, + PGPPrivateKey key) + throws PGPException + { + contentSigner = contentSignerBuilder.build(signatureType, key); + sigOut = contentSigner.getOutputStream(); + sigType = contentSigner.getType(); + lastb = 0; + + if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm()) + { + throw new PGPException("key algorithm mismatch"); + } + } + + /** + * Initialise the generator for signing. + * + * @param signatureType + * @param key + * @param random + * @throws PGPException + * @deprecated random parameter now ignored. + */ + public void initSign( + int signatureType, + PGPPrivateKey key, + SecureRandom random) + throws PGPException + { + initSign(signatureType, key); + } + + public void update( + byte b) + throws SignatureException + { + if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + if (b == '\r') + { + byteUpdate((byte)'\r'); + byteUpdate((byte)'\n'); + } + else if (b == '\n') + { + if (lastb != '\r') + { + byteUpdate((byte)'\r'); + byteUpdate((byte)'\n'); + } + } + else + { + byteUpdate(b); + } + + lastb = b; + } + else + { + byteUpdate(b); + } + } + + public void update( + byte[] b) + throws SignatureException + { + this.update(b, 0, b.length); + } + + public void update( + byte[] b, + int off, + int len) + throws SignatureException + { + if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + int finish = off + len; + + for (int i = off; i != finish; i++) + { + this.update(b[i]); + } + } + else + { + blockUpdate(b, off, len); + } + } + + private void byteUpdate(byte b) + throws SignatureException + { + try + { + sigOut.write(b); + } + catch (IOException e) + { // TODO: we really should get rid of signature exception next.... + throw new SignatureException(e.getMessage()); + } + } + + private void blockUpdate(byte[] block, int off, int len) + throws SignatureException + { + try + { + sigOut.write(block, off, len); + } + catch (IOException e) + { + throw new IllegalStateException(e.getMessage()); + } + } + + public void setHashedSubpackets( + PGPSignatureSubpacketVector hashedPcks) + { + if (hashedPcks == null) + { + hashed = new SignatureSubpacket[0]; + return; + } + + hashed = hashedPcks.toSubpacketArray(); + } + + public void setUnhashedSubpackets( + PGPSignatureSubpacketVector unhashedPcks) + { + if (unhashedPcks == null) + { + unhashed = new SignatureSubpacket[0]; + return; + } + + unhashed = unhashedPcks.toSubpacketArray(); + } + + /** + * Return the one pass header associated with the current signature. + * + * @param isNested + * @return PGPOnePassSignature + * @throws PGPException + */ + public PGPOnePassSignature generateOnePassVersion( + boolean isNested) + throws PGPException + { + return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested)); + } + + /** + * Return a signature object containing the current signature state. + * + * @return PGPSignature + * @throws PGPException + * @throws SignatureException + */ + public PGPSignature generate() + throws PGPException, SignatureException + { + MPInteger[] sigValues; + int version = 4; + ByteArrayOutputStream sOut = new ByteArrayOutputStream(); + SignatureSubpacket[] hPkts, unhPkts; + + if (!packetPresent(hashed, SignatureSubpacketTags.CREATION_TIME)) + { + hPkts = insertSubpacket(hashed, new SignatureCreationTime(false, new Date())); + } + else + { + hPkts = hashed; + } + + if (!packetPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && !packetPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID)) + { + unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, contentSigner.getKeyID())); + } + else + { + unhPkts = unhashed; + } + + try + { + sOut.write((byte)version); + sOut.write((byte)sigType); + sOut.write((byte)contentSigner.getKeyAlgorithm()); + sOut.write((byte)contentSigner.getHashAlgorithm()); + + ByteArrayOutputStream hOut = new ByteArrayOutputStream(); + + for (int i = 0; i != hPkts.length; i++) + { + hPkts[i].encode(hOut); + } + + byte[] data = hOut.toByteArray(); + + sOut.write((byte)(data.length >> 8)); + sOut.write((byte)data.length); + sOut.write(data); + } + catch (IOException e) + { + throw new PGPException("exception encoding hashed data.", e); + } + + byte[] hData = sOut.toByteArray(); + + sOut.write((byte)version); + sOut.write((byte)0xff); + sOut.write((byte)(hData.length >> 24)); + sOut.write((byte)(hData.length >> 16)); + sOut.write((byte)(hData.length >> 8)); + sOut.write((byte)(hData.length)); + + byte[] trailer = sOut.toByteArray(); + + blockUpdate(trailer, 0, trailer.length); + + if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN + || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL) // an RSA signature + { + sigValues = new MPInteger[1]; + sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature())); + } + else + { + sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature()); + } + + byte[] digest = contentSigner.getDigest(); + byte[] fingerPrint = new byte[2]; + + fingerPrint[0] = digest[0]; + fingerPrint[1] = digest[1]; + + return new PGPSignature(new SignaturePacket(sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, sigValues)); + } + + /** + * Generate a certification for the passed in id and key. + * + * @param id the id we are certifying against the public key. + * @param pubKey the key we are certifying against the id. + * @return the certification. + * @throws SignatureException + * @throws PGPException + */ + public PGPSignature generateCertification( + String id, + PGPPublicKey pubKey) + throws SignatureException, PGPException + { + updateWithPublicKey(pubKey); + + // + // hash in the id + // + updateWithIdData(0xb4, Strings.toUTF8ByteArray(id)); + + return this.generate(); + } + + /** + * Generate a certification for the passed in userAttributes + * @param userAttributes the id we are certifying against the public key. + * @param pubKey the key we are certifying against the id. + * @return the certification. + * @throws SignatureException + * @throws PGPException + */ + public PGPSignature generateCertification( + PGPUserAttributeSubpacketVector userAttributes, + PGPPublicKey pubKey) + throws SignatureException, PGPException + { + updateWithPublicKey(pubKey); + + // + // hash in the attributes + // + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + UserAttributeSubpacket[] packets = userAttributes.toSubpacketArray(); + for (int i = 0; i != packets.length; i++) + { + packets[i].encode(bOut); + } + updateWithIdData(0xd1, bOut.toByteArray()); + } + catch (IOException e) + { + throw new PGPException("cannot encode subpacket array", e); + } + + return this.generate(); + } + + /** + * Generate a certification for the passed in key against the passed in + * master key. + * + * @param masterKey the key we are certifying against. + * @param pubKey the key we are certifying. + * @return the certification. + * @throws SignatureException + * @throws PGPException + */ + public PGPSignature generateCertification( + PGPPublicKey masterKey, + PGPPublicKey pubKey) + throws SignatureException, PGPException + { + updateWithPublicKey(masterKey); + updateWithPublicKey(pubKey); + + return this.generate(); + } + + /** + * Generate a certification, such as a revocation, for the passed in key. + * + * @param pubKey the key we are certifying. + * @return the certification. + * @throws SignatureException + * @throws PGPException + */ + public PGPSignature generateCertification( + PGPPublicKey pubKey) + throws SignatureException, PGPException + { + if ((sigType == PGPSignature.SUBKEY_REVOCATION || sigType == PGPSignature.SUBKEY_BINDING) && !pubKey.isMasterKey()) + { + throw new IllegalArgumentException("certifications involving subkey requires public key of revoking key as well."); + } + + updateWithPublicKey(pubKey); + + return this.generate(); + } + + private byte[] getEncodedPublicKey( + PGPPublicKey pubKey) + throws PGPException + { + byte[] keyBytes; + + try + { + keyBytes = pubKey.publicPk.getEncodedContents(); + } + catch (IOException e) + { + throw new PGPException("exception preparing key.", e); + } + + return keyBytes; + } + + private boolean packetPresent( + SignatureSubpacket[] packets, + int type) + { + for (int i = 0; i != packets.length; i++) + { + if (packets[i].getType() == type) + { + return true; + } + } + + return false; + } + + private SignatureSubpacket[] insertSubpacket( + SignatureSubpacket[] packets, + SignatureSubpacket subpacket) + { + SignatureSubpacket[] tmp = new SignatureSubpacket[packets.length + 1]; + + tmp[0] = subpacket; + System.arraycopy(packets, 0, tmp, 1, packets.length); + + return tmp; + } + + private void updateWithIdData(int header, byte[] idBytes) + throws SignatureException + { + this.update((byte)header); + this.update((byte)(idBytes.length >> 24)); + this.update((byte)(idBytes.length >> 16)); + this.update((byte)(idBytes.length >> 8)); + this.update((byte)(idBytes.length)); + this.update(idBytes); + } + + private void updateWithPublicKey(PGPPublicKey key) + throws PGPException, SignatureException + { + byte[] keyBytes = getEncodedPublicKey(key); + + this.update((byte)0x99); + this.update((byte)(keyBytes.length >> 8)); + this.update((byte)(keyBytes.length)); + this.update(keyBytes); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureList.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureList.java new file mode 100644 index 000000000..a7f6b8cf3 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureList.java @@ -0,0 +1,40 @@ +package org.spongycastle.openpgp; + +/** + * A list of PGP signatures - normally in the signature block after literal data. + */ +public class PGPSignatureList +{ + PGPSignature[] sigs; + + public PGPSignatureList( + PGPSignature[] sigs) + { + this.sigs = new PGPSignature[sigs.length]; + + System.arraycopy(sigs, 0, this.sigs, 0, sigs.length); + } + + public PGPSignatureList( + PGPSignature sig) + { + this.sigs = new PGPSignature[1]; + this.sigs[0] = sig; + } + + public PGPSignature get( + int index) + { + return sigs[index]; + } + + public int size() + { + return sigs.length; + } + + public boolean isEmpty() + { + return (sigs.length == 0); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketGenerator.java new file mode 100644 index 000000000..f88b733a2 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketGenerator.java @@ -0,0 +1,197 @@ +package org.spongycastle.openpgp; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.sig.EmbeddedSignature; +import org.spongycastle.bcpg.sig.Exportable; +import org.spongycastle.bcpg.sig.Features; +import org.spongycastle.bcpg.sig.IssuerKeyID; +import org.spongycastle.bcpg.sig.KeyExpirationTime; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.bcpg.sig.NotationData; +import org.spongycastle.bcpg.sig.PreferredAlgorithms; +import org.spongycastle.bcpg.sig.PrimaryUserID; +import org.spongycastle.bcpg.sig.Revocable; +import org.spongycastle.bcpg.sig.RevocationKey; +import org.spongycastle.bcpg.sig.RevocationKeyTags; +import org.spongycastle.bcpg.sig.RevocationReason; +import org.spongycastle.bcpg.sig.SignatureCreationTime; +import org.spongycastle.bcpg.sig.SignatureExpirationTime; +import org.spongycastle.bcpg.sig.SignerUserID; +import org.spongycastle.bcpg.sig.TrustSignature; + +/** + * Generator for signature subpackets. + */ +public class PGPSignatureSubpacketGenerator +{ + List list = new ArrayList(); + + public PGPSignatureSubpacketGenerator() + { + } + + public void setRevocable(boolean isCritical, boolean isRevocable) + { + list.add(new Revocable(isCritical, isRevocable)); + } + + public void setExportable(boolean isCritical, boolean isExportable) + { + list.add(new Exportable(isCritical, isExportable)); + } + + public void setFeature(boolean isCritical, byte feature) + { + list.add(new Features(isCritical, feature)); + } + + /** + * Add a TrustSignature packet to the signature. The values for depth and trust are + * largely installation dependent but there are some guidelines in RFC 4880 - + * 5.2.3.13. + * + * @param isCritical true if the packet is critical. + * @param depth depth level. + * @param trustAmount trust amount. + */ + public void setTrust(boolean isCritical, int depth, int trustAmount) + { + list.add(new TrustSignature(isCritical, depth, trustAmount)); + } + + /** + * Set the number of seconds a key is valid for after the time of its creation. A + * value of zero means the key never expires. + * + * @param isCritical true if should be treated as critical, false otherwise. + * @param seconds + */ + public void setKeyExpirationTime(boolean isCritical, long seconds) + { + list.add(new KeyExpirationTime(isCritical, seconds)); + } + + /** + * Set the number of seconds a signature is valid for after the time of its creation. + * A value of zero means the signature never expires. + * + * @param isCritical true if should be treated as critical, false otherwise. + * @param seconds + */ + public void setSignatureExpirationTime(boolean isCritical, long seconds) + { + list.add(new SignatureExpirationTime(isCritical, seconds)); + } + + /** + * Set the creation time for the signature. + * <p> + * Note: this overrides the generation of a creation time when the signature is + * generated. + */ + public void setSignatureCreationTime(boolean isCritical, Date date) + { + list.add(new SignatureCreationTime(isCritical, date)); + } + + public void setPreferredHashAlgorithms(boolean isCritical, int[] algorithms) + { + list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_HASH_ALGS, isCritical, + algorithms)); + } + + public void setPreferredSymmetricAlgorithms(boolean isCritical, int[] algorithms) + { + list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_SYM_ALGS, isCritical, + algorithms)); + } + + public void setPreferredCompressionAlgorithms(boolean isCritical, int[] algorithms) + { + list.add(new PreferredAlgorithms(SignatureSubpacketTags.PREFERRED_COMP_ALGS, isCritical, + algorithms)); + } + + public void setKeyFlags(boolean isCritical, int flags) + { + list.add(new KeyFlags(isCritical, flags)); + } + + public void setSignerUserID(boolean isCritical, String userID) + { + if (userID == null) + { + throw new IllegalArgumentException("attempt to set null SignerUserID"); + } + + list.add(new SignerUserID(isCritical, userID)); + } + + public void setEmbeddedSignature(boolean isCritical, PGPSignature pgpSignature) + throws IOException + { + byte[] sig = pgpSignature.getEncoded(); + byte[] data; + + if (sig.length - 1 > 256) + { + data = new byte[sig.length - 3]; + } + else + { + data = new byte[sig.length - 2]; + } + + System.arraycopy(sig, sig.length - data.length, data, 0, data.length); + + list.add(new EmbeddedSignature(isCritical, data)); + } + + public void setPrimaryUserID(boolean isCritical, boolean isPrimaryUserID) + { + list.add(new PrimaryUserID(isCritical, isPrimaryUserID)); + } + + public void setNotationData(boolean isCritical, boolean isHumanReadable, String notationName, + String notationValue) + { + list.add(new NotationData(isCritical, isHumanReadable, notationName, notationValue)); + } + + /** + * Sets revocation reason sub packet + */ + public void setRevocationReason(boolean isCritical, byte reason, String description) + { + list.add(new RevocationReason(isCritical, reason, description)); + } + + /** + * Sets revocation key sub packet + */ + public void setRevocationKey(boolean isCritical, int keyAlgorithm, byte[] fingerprint) + { + list.add(new RevocationKey(isCritical, RevocationKeyTags.CLASS_DEFAULT, keyAlgorithm, + fingerprint)); + } + + /** + * Sets issuer key sub packe + */ + public void setIssuerKeyID(boolean isCritical, long keyID) + { + list.add(new IssuerKeyID(isCritical, keyID)); + } + + public PGPSignatureSubpacketVector generate() + { + return new PGPSignatureSubpacketVector( + (SignatureSubpacket[])list.toArray(new SignatureSubpacket[list.size()])); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketVector.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketVector.java new file mode 100644 index 000000000..738828997 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPSignatureSubpacketVector.java @@ -0,0 +1,277 @@ +package org.spongycastle.openpgp; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.spongycastle.bcpg.SignatureSubpacket; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.sig.Features; +import org.spongycastle.bcpg.sig.IssuerKeyID; +import org.spongycastle.bcpg.sig.KeyExpirationTime; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.bcpg.sig.NotationData; +import org.spongycastle.bcpg.sig.PreferredAlgorithms; +import org.spongycastle.bcpg.sig.PrimaryUserID; +import org.spongycastle.bcpg.sig.SignatureCreationTime; +import org.spongycastle.bcpg.sig.SignatureExpirationTime; +import org.spongycastle.bcpg.sig.SignerUserID; + +/** + * Container for a list of signature subpackets. + */ +public class PGPSignatureSubpacketVector +{ + SignatureSubpacket[] packets; + + PGPSignatureSubpacketVector( + SignatureSubpacket[] packets) + { + this.packets = packets; + } + + public SignatureSubpacket getSubpacket( + int type) + { + for (int i = 0; i != packets.length; i++) + { + if (packets[i].getType() == type) + { + return packets[i]; + } + } + + return null; + } + + /** + * Return true if a particular subpacket type exists. + * + * @param type type to look for. + * @return true if present, false otherwise. + */ + public boolean hasSubpacket( + int type) + { + return getSubpacket(type) != null; + } + + /** + * Return all signature subpackets of the passed in type. + * @param type subpacket type code + * @return an array of zero or more matching subpackets. + */ + public SignatureSubpacket[] getSubpackets( + int type) + { + List list = new ArrayList(); + + for (int i = 0; i != packets.length; i++) + { + if (packets[i].getType() == type) + { + list.add(packets[i]); + } + } + + return (SignatureSubpacket[])list.toArray(new SignatureSubpacket[]{}); + } + + public NotationData[] getNotationDataOccurences() + { + SignatureSubpacket[] notations = getSubpackets(SignatureSubpacketTags.NOTATION_DATA); + NotationData[] vals = new NotationData[notations.length]; + for (int i = 0; i < notations.length; i++) + { + vals[i] = (NotationData)notations[i]; + } + + return vals; + } + + public long getIssuerKeyID() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID); + + if (p == null) + { + return 0; + } + + return ((IssuerKeyID)p).getKeyID(); + } + + public Date getSignatureCreationTime() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.CREATION_TIME); + + if (p == null) + { + return null; + } + + return ((SignatureCreationTime)p).getTime(); + } + + /** + * Return the number of seconds a signature is valid for after its creation date. A value of zero means + * the signature never expires. + * + * @return seconds a signature is valid for. + */ + public long getSignatureExpirationTime() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.EXPIRE_TIME); + + if (p == null) + { + return 0; + } + + return ((SignatureExpirationTime)p).getTime(); + } + + /** + * Return the number of seconds a key is valid for after its creation date. A value of zero means + * the key never expires. + * + * @return seconds a key is valid for. + */ + public long getKeyExpirationTime() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.KEY_EXPIRE_TIME); + + if (p == null) + { + return 0; + } + + return ((KeyExpirationTime)p).getTime(); + } + + public int[] getPreferredHashAlgorithms() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_HASH_ALGS); + + if (p == null) + { + return null; + } + + return ((PreferredAlgorithms)p).getPreferences(); + } + + public int[] getPreferredSymmetricAlgorithms() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_SYM_ALGS); + + if (p == null) + { + return null; + } + + return ((PreferredAlgorithms)p).getPreferences(); + } + + public int[] getPreferredCompressionAlgorithms() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.PREFERRED_COMP_ALGS); + + if (p == null) + { + return null; + } + + return ((PreferredAlgorithms)p).getPreferences(); + } + + public int getKeyFlags() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.KEY_FLAGS); + + if (p == null) + { + return 0; + } + + return ((KeyFlags)p).getFlags(); + } + + public String getSignerUserID() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.SIGNER_USER_ID); + + if (p == null) + { + return null; + } + + return ((SignerUserID)p).getID(); + } + + public boolean isPrimaryUserID() + { + PrimaryUserID primaryId = (PrimaryUserID)this.getSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID); + + if (primaryId != null) + { + return primaryId.isPrimaryUserID(); + } + + return false; + } + + public int[] getCriticalTags() + { + int count = 0; + + for (int i = 0; i != packets.length; i++) + { + if (packets[i].isCritical()) + { + count++; + } + } + + int[] list = new int[count]; + + count = 0; + + for (int i = 0; i != packets.length; i++) + { + if (packets[i].isCritical()) + { + list[count++] = packets[i].getType(); + } + } + + return list; + } + + public Features getFeatures() + { + SignatureSubpacket p = this.getSubpacket(SignatureSubpacketTags.FEATURES); + + if (p == null) + { + return null; + } + + return new Features(p.isCritical(), p.getData()); + } + + /** + * Return the number of packets this vector contains. + * + * @return size of the packet vector. + */ + public int size() + { + return packets.length; + } + + SignatureSubpacket[] toSubpacketArray() + { + return packets; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVector.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVector.java new file mode 100644 index 000000000..26d6c7368 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVector.java @@ -0,0 +1,93 @@ +package org.spongycastle.openpgp; + +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.bcpg.UserAttributeSubpacketTags; +import org.spongycastle.bcpg.attr.ImageAttribute; + +/** + * Container for a list of user attribute subpackets. + */ +public class PGPUserAttributeSubpacketVector +{ + UserAttributeSubpacket[] packets; + + PGPUserAttributeSubpacketVector( + UserAttributeSubpacket[] packets) + { + this.packets = packets; + } + + public UserAttributeSubpacket getSubpacket( + int type) + { + for (int i = 0; i != packets.length; i++) + { + if (packets[i].getType() == type) + { + return packets[i]; + } + } + + return null; + } + + public ImageAttribute getImageAttribute() + { + UserAttributeSubpacket p = this.getSubpacket(UserAttributeSubpacketTags.IMAGE_ATTRIBUTE); + + if (p == null) + { + return null; + } + + return (ImageAttribute)p; + } + + UserAttributeSubpacket[] toSubpacketArray() + { + return packets; + } + + public boolean equals( + Object o) + { + if (o == this) + { + return true; + } + + if (o instanceof PGPUserAttributeSubpacketVector) + { + PGPUserAttributeSubpacketVector other = (PGPUserAttributeSubpacketVector)o; + + if (other.packets.length != packets.length) + { + return false; + } + + for (int i = 0; i != packets.length; i++) + { + if (!other.packets[i].equals(packets[i])) + { + return false; + } + } + + return true; + } + + return false; + } + + public int hashCode() + { + int code = 0; + + for (int i = 0; i != packets.length; i++) + { + code ^= packets[i].hashCode(); + } + + return code; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java new file mode 100644 index 000000000..66af84b33 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUserAttributeSubpacketVectorGenerator.java @@ -0,0 +1,27 @@ +package org.spongycastle.openpgp; + +import org.spongycastle.bcpg.UserAttributeSubpacket; +import org.spongycastle.bcpg.attr.ImageAttribute; + +import java.util.ArrayList; +import java.util.List; + +public class PGPUserAttributeSubpacketVectorGenerator +{ + private List list = new ArrayList(); + + public void setImageAttribute(int imageType, byte[] imageData) + { + if (imageData == null) + { + throw new IllegalArgumentException("attempt to set null image"); + } + + list.add(new ImageAttribute(imageType, imageData)); + } + + public PGPUserAttributeSubpacketVector generate() + { + return new PGPUserAttributeSubpacketVector((UserAttributeSubpacket[])list.toArray(new UserAttributeSubpacket[list.size()])); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUtil.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUtil.java new file mode 100644 index 000000000..31f4ed772 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPUtil.java @@ -0,0 +1,376 @@ +package org.spongycastle.openpgp; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Date; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Sequence; +import org.spongycastle.asn1.DERInteger; +import org.spongycastle.bcpg.ArmoredInputStream; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.util.encoders.Base64; + +/** + * Basic utility class + */ +public class PGPUtil + implements HashAlgorithmTags +{ + private static String defProvider = "SC"; + + /** + * Return the provider that will be used by factory classes in situations + * where a provider must be determined on the fly. + * + * @return String + */ + public static String getDefaultProvider() + { + return defProvider; + } + + /** + * Set the provider to be used by the package when it is necessary to + * find one on the fly. + * + * @param provider + */ + public static void setDefaultProvider( + String provider) + { + defProvider = provider; + } + + static MPInteger[] dsaSigToMpi( + byte[] encoding) + throws PGPException + { + ASN1InputStream aIn = new ASN1InputStream(encoding); + + DERInteger i1; + DERInteger i2; + + try + { + ASN1Sequence s = (ASN1Sequence)aIn.readObject(); + + i1 = (DERInteger)s.getObjectAt(0); + i2 = (DERInteger)s.getObjectAt(1); + } + catch (IOException e) + { + throw new PGPException("exception encoding signature", e); + } + + MPInteger[] values = new MPInteger[2]; + + values[0] = new MPInteger(i1.getValue()); + values[1] = new MPInteger(i2.getValue()); + + return values; + } + + static String getDigestName( + int hashAlgorithm) + throws PGPException + { + switch (hashAlgorithm) + { + case HashAlgorithmTags.SHA1: + return "SHA1"; + case HashAlgorithmTags.MD2: + return "MD2"; + case HashAlgorithmTags.MD5: + return "MD5"; + case HashAlgorithmTags.RIPEMD160: + return "RIPEMD160"; + case HashAlgorithmTags.SHA256: + return "SHA256"; + case HashAlgorithmTags.SHA384: + return "SHA384"; + case HashAlgorithmTags.SHA512: + return "SHA512"; + case HashAlgorithmTags.SHA224: + return "SHA224"; + default: + throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm); + } + } + + static String getSignatureName( + int keyAlgorithm, + int hashAlgorithm) + throws PGPException + { + String encAlg; + + switch (keyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + encAlg = "RSA"; + break; + case PublicKeyAlgorithmTags.DSA: + encAlg = "DSA"; + break; + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases. + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + encAlg = "ElGamal"; + break; + default: + throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm); + } + + return getDigestName(hashAlgorithm) + "with" + encAlg; + } + + public static byte[] makeRandomKey( + int algorithm, + SecureRandom random) + throws PGPException + { + int keySize = 0; + + switch (algorithm) + { + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + keySize = 192; + break; + case SymmetricKeyAlgorithmTags.IDEA: + keySize = 128; + break; + case SymmetricKeyAlgorithmTags.CAST5: + keySize = 128; + break; + case SymmetricKeyAlgorithmTags.BLOWFISH: + keySize = 128; + break; + case SymmetricKeyAlgorithmTags.SAFER: + keySize = 128; + break; + case SymmetricKeyAlgorithmTags.DES: + keySize = 64; + break; + case SymmetricKeyAlgorithmTags.AES_128: + keySize = 128; + break; + case SymmetricKeyAlgorithmTags.AES_192: + keySize = 192; + break; + case SymmetricKeyAlgorithmTags.AES_256: + keySize = 256; + break; + case SymmetricKeyAlgorithmTags.TWOFISH: + keySize = 256; + break; + default: + throw new PGPException("unknown symmetric algorithm: " + algorithm); + } + + byte[] keyBytes = new byte[(keySize + 7) / 8]; + + random.nextBytes(keyBytes); + + return keyBytes; + } + + /** + * write out the passed in file as a literal data packet. + * + * @param out + * @param fileType the LiteralData type for the file. + * @param file + * + * @throws IOException + */ + public static void writeFileToLiteralData( + OutputStream out, + char fileType, + File file) + throws IOException + { + PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); + OutputStream pOut = lData.open(out, fileType, file.getName(), file.length(), new Date(file.lastModified())); + pipeFileContents(file, pOut, 4096); + } + + /** + * write out the passed in file as a literal data packet in partial packet format. + * + * @param out + * @param fileType the LiteralData type for the file. + * @param file + * @param buffer buffer to be used to chunk the file into partial packets. + * + * @throws IOException + */ + public static void writeFileToLiteralData( + OutputStream out, + char fileType, + File file, + byte[] buffer) + throws IOException + { + PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); + OutputStream pOut = lData.open(out, fileType, file.getName(), new Date(file.lastModified()), buffer); + pipeFileContents(file, pOut, buffer.length); + } + + private static void pipeFileContents(File file, OutputStream pOut, int bufSize) throws IOException + { + FileInputStream in = new FileInputStream(file); + byte[] buf = new byte[bufSize]; + + int len; + while ((len = in.read(buf)) > 0) + { + pOut.write(buf, 0, len); + } + + pOut.close(); + in.close(); + } + + private static final int READ_AHEAD = 60; + + private static boolean isPossiblyBase64( + int ch) + { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') + || (ch >= '0' && ch <= '9') || (ch == '+') || (ch == '/') + || (ch == '\r') || (ch == '\n'); + } + + /** + * Return either an ArmoredInputStream or a BCPGInputStream based on + * whether the initial characters of the stream are binary PGP encodings or not. + * + * @param in the stream to be wrapped + * @return a BCPGInputStream + * @throws IOException + */ + public static InputStream getDecoderStream( + InputStream in) + throws IOException + { + if (!in.markSupported()) + { + in = new BufferedInputStreamExt(in); + } + + in.mark(READ_AHEAD); + + int ch = in.read(); + + + if ((ch & 0x80) != 0) + { + in.reset(); + + return in; + } + else + { + if (!isPossiblyBase64(ch)) + { + in.reset(); + + return new ArmoredInputStream(in); + } + + byte[] buf = new byte[READ_AHEAD]; + int count = 1; + int index = 1; + + buf[0] = (byte)ch; + while (count != READ_AHEAD && (ch = in.read()) >= 0) + { + if (!isPossiblyBase64(ch)) + { + in.reset(); + + return new ArmoredInputStream(in); + } + + if (ch != '\n' && ch != '\r') + { + buf[index++] = (byte)ch; + } + + count++; + } + + in.reset(); + + // + // nothing but new lines, little else, assume regular armoring + // + if (count < 4) + { + return new ArmoredInputStream(in); + } + + // + // test our non-blank data + // + byte[] firstBlock = new byte[8]; + + System.arraycopy(buf, 0, firstBlock, 0, firstBlock.length); + + byte[] decoded = Base64.decode(firstBlock); + + // + // it's a base64 PGP block. + // + if ((decoded[0] & 0x80) != 0) + { + return new ArmoredInputStream(in, false); + } + + return new ArmoredInputStream(in); + } + } + + static Provider getProvider(String providerName) + throws NoSuchProviderException + { + Provider prov = Security.getProvider(providerName); + + if (prov == null) + { + throw new NoSuchProviderException("provider " + providerName + " not found."); + } + + return prov; + } + + static class BufferedInputStreamExt extends BufferedInputStream + { + BufferedInputStreamExt(InputStream input) + { + super(input); + } + + public synchronized int available() throws IOException + { + int result = super.available(); + if (result < 0) + { + result = Integer.MAX_VALUE; + } + return result; + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPV3SignatureGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPV3SignatureGenerator.java new file mode 100644 index 000000000..c8b837b58 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/PGPV3SignatureGenerator.java @@ -0,0 +1,286 @@ +package org.spongycastle.openpgp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.util.Date; + +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.OnePassSignaturePacket; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SignaturePacket; +import org.spongycastle.openpgp.operator.PGPContentSigner; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; + +/** + * Generator for old style PGP V3 Signatures. + */ +public class PGPV3SignatureGenerator +{ + private byte lastb; + private OutputStream sigOut; + private PGPContentSignerBuilder contentSignerBuilder; + private PGPContentSigner contentSigner; + private int sigType; + private int providedKeyAlgorithm = -1; + + /** + * Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. + * + * @param keyAlgorithm + * @param hashAlgorithm + * @param provider + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws PGPException + * @deprecated use constructor taking PGPContentSignerBuilder. + */ + public PGPV3SignatureGenerator( + int keyAlgorithm, + int hashAlgorithm, + String provider) + throws NoSuchAlgorithmException, NoSuchProviderException, PGPException + { + this(keyAlgorithm, hashAlgorithm, PGPUtil.getProvider(provider)); + } + + /** + * + * @param keyAlgorithm + * @param hashAlgorithm + * @param provider + * @throws NoSuchAlgorithmException + * @throws PGPException + * @deprecated use constructor taking PGPContentSignerBuilder. + */ + public PGPV3SignatureGenerator( + int keyAlgorithm, + int hashAlgorithm, + Provider provider) + throws NoSuchAlgorithmException, PGPException + { + this.providedKeyAlgorithm = keyAlgorithm; + this.contentSignerBuilder = new JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm).setProvider(provider); + } + + /** + * Create a signature generator built on the passed in contentSignerBuilder. + * + * @param contentSignerBuilder builder to produce PGPContentSigner objects for generating signatures. + */ + public PGPV3SignatureGenerator( + PGPContentSignerBuilder contentSignerBuilder) + { + this.contentSignerBuilder = contentSignerBuilder; + } + + /** + * Initialise the generator for signing. + * + * @param signatureType + * @param key + * @throws PGPException + */ + public void init( + int signatureType, + PGPPrivateKey key) + throws PGPException + { + contentSigner = contentSignerBuilder.build(signatureType, key); + sigOut = contentSigner.getOutputStream(); + sigType = contentSigner.getType(); + lastb = 0; + + if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm()) + { + throw new PGPException("key algorithm mismatch"); + } + } + + /** + * Initialise the generator for signing. + * + * @param signatureType + * @param key + * @param random + * @throws PGPException + * @deprecated random now ignored - set random in PGPContentSignerBuilder + */ + public void initSign( + int signatureType, + PGPPrivateKey key, + SecureRandom random) + throws PGPException + { + init(signatureType, key); + } + + /** + * Initialise the generator for signing. + * + * @param signatureType + * @param key + * @throws PGPException + * @deprecated use init() + */ + public void initSign( + int signatureType, + PGPPrivateKey key) + throws PGPException + { + init(signatureType, key); + } + + public void update( + byte b) + throws SignatureException + { + if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + if (b == '\r') + { + byteUpdate((byte)'\r'); + byteUpdate((byte)'\n'); + } + else if (b == '\n') + { + if (lastb != '\r') + { + byteUpdate((byte)'\r'); + byteUpdate((byte)'\n'); + } + } + else + { + byteUpdate(b); + } + + lastb = b; + } + else + { + byteUpdate(b); + } + } + + public void update( + byte[] b) + throws SignatureException + { + this.update(b, 0, b.length); + } + + public void update( + byte[] b, + int off, + int len) + throws SignatureException + { + if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT) + { + int finish = off + len; + + for (int i = off; i != finish; i++) + { + this.update(b[i]); + } + } + else + { + blockUpdate(b, off, len); + } + } + + private void byteUpdate(byte b) + throws SignatureException + { + try + { + sigOut.write(b); + } + catch (IOException e) + { + throw new IllegalStateException("unable to update signature"); + } + } + + private void blockUpdate(byte[] block, int off, int len) + throws SignatureException + { + try + { + sigOut.write(block, off, len); + } + catch (IOException e) + { + throw new IllegalStateException("unable to update signature"); + } + } + + /** + * Return the one pass header associated with the current signature. + * + * @param isNested + * @return PGPOnePassSignature + * @throws PGPException + */ + public PGPOnePassSignature generateOnePassVersion( + boolean isNested) + throws PGPException + { + return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested)); + } + + /** + * Return a V3 signature object containing the current signature state. + * + * @return PGPSignature + * @throws PGPException + * @throws SignatureException + */ + public PGPSignature generate() + throws PGPException, SignatureException + { + long creationTime = new Date().getTime() / 1000; + + ByteArrayOutputStream sOut = new ByteArrayOutputStream(); + + sOut.write(sigType); + sOut.write((byte)(creationTime >> 24)); + sOut.write((byte)(creationTime >> 16)); + sOut.write((byte)(creationTime >> 8)); + sOut.write((byte)creationTime); + + byte[] hData = sOut.toByteArray(); + + blockUpdate(hData, 0, hData.length); + + MPInteger[] sigValues; + if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN + || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL) + // an RSA signature + { + sigValues = new MPInteger[1]; + sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature())); + } + else + { + sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature()); + } + + byte[] digest = contentSigner.getDigest(); + byte[] fingerPrint = new byte[2]; + + fingerPrint[0] = digest[0]; + fingerPrint[1] = digest[1]; + + return new PGPSignature(new SignaturePacket(3, contentSigner.getType(), contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), creationTime * 1000, fingerPrint, sigValues)); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/StreamGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/StreamGenerator.java new file mode 100644 index 000000000..9ee3d4659 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/StreamGenerator.java @@ -0,0 +1,9 @@ +package org.spongycastle.openpgp; + +import java.io.IOException; + +interface StreamGenerator +{ + void close() + throws IOException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/WrappedGeneratorStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/WrappedGeneratorStream.java new file mode 100644 index 000000000..f5360d51b --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/WrappedGeneratorStream.java @@ -0,0 +1,46 @@ +package org.spongycastle.openpgp; + +import java.io.IOException; +import java.io.OutputStream; + +class WrappedGeneratorStream + extends OutputStream +{ + private final OutputStream _out; + private final StreamGenerator _sGen; + + public WrappedGeneratorStream(OutputStream out, StreamGenerator sGen) + { + _out = out; + _sGen = sGen; + } + public void write(byte[] bytes) + throws IOException + { + _out.write(bytes); + } + + public void write(byte[] bytes, int offset, int length) + throws IOException + { + _out.write(bytes, offset, length); + } + + public void write(int b) + throws IOException + { + _out.write(b); + } + + public void flush() + throws IOException + { + _out.flush(); + } + + public void close() + throws IOException + { + _sGen.close(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/ByteArrayHandler.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/ByteArrayHandler.java new file mode 100644 index 000000000..948af4028 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/ByteArrayHandler.java @@ -0,0 +1,206 @@ +package org.spongycastle.openpgp.examples; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Date; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPLiteralDataGenerator; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPBEEncryptedData; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.util.io.Streams; + +/** + * Simple routine to encrypt and decrypt using a passphrase. + * This service routine provides the basic PGP services between + * byte arrays. + * + * Note: this code plays no attention to -CONSOLE in the file name + * the specification of "_CONSOLE" in the filename. + * It also expects that a single pass phrase will have been used. + * + */ +public class ByteArrayHandler +{ + /** + * decrypt the passed in message stream + * + * @param encrypted The message to be decrypted. + * @param passPhrase Pass phrase (key) + * + * @return Clear text as a byte array. I18N considerations are + * not handled by this routine + * @exception IOException + * @exception PGPException + * @exception NoSuchProviderException + */ + public static byte[] decrypt( + byte[] encrypted, + char[] passPhrase) + throws IOException, PGPException, NoSuchProviderException + { + InputStream in = new ByteArrayInputStream(encrypted); + + in = PGPUtil.getDecoderStream(in); + + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + // + // the first object might be a PGP marker packet. + // + if (o instanceof PGPEncryptedDataList) + { + enc = (PGPEncryptedDataList)o; + } + else + { + enc = (PGPEncryptedDataList)pgpF.nextObject(); + } + + PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0); + + InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("SC").build()).setProvider("SC").build(passPhrase)); + + PGPObjectFactory pgpFact = new PGPObjectFactory(clear); + + PGPCompressedData cData = (PGPCompressedData)pgpFact.nextObject(); + + pgpFact = new PGPObjectFactory(cData.getDataStream()); + + PGPLiteralData ld = (PGPLiteralData)pgpFact.nextObject(); + + return Streams.readAll(ld.getInputStream()); + } + + /** + * Simple PGP encryptor between byte[]. + * + * @param clearData The test to be encrypted + * @param passPhrase The pass phrase (key). This method assumes that the + * key is a simple pass phrase, and does not yet support + * RSA or more sophisiticated keying. + * @param fileName File name. This is used in the Literal Data Packet (tag 11) + * which is really inly important if the data is to be + * related to a file to be recovered later. Because this + * routine does not know the source of the information, the + * caller can set something here for file name use that + * will be carried. If this routine is being used to + * encrypt SOAP MIME bodies, for example, use the file name from the + * MIME type, if applicable. Or anything else appropriate. + * + * @param armor + * + * @return encrypted data. + * @exception IOException + * @exception PGPException + * @exception NoSuchProviderException + */ + public static byte[] encrypt( + byte[] clearData, + char[] passPhrase, + String fileName, + int algorithm, + boolean armor) + throws IOException, PGPException, NoSuchProviderException + { + if (fileName == null) + { + fileName= PGPLiteralData.CONSOLE; + } + + byte[] compressedData = compress(clearData, fileName, CompressionAlgorithmTags.ZIP); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + OutputStream out = bOut; + if (armor) + { + out = new ArmoredOutputStream(out); + } + + PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(algorithm).setSecureRandom(new SecureRandom()).setProvider("SC")); + encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase).setProvider("SC")); + + OutputStream encOut = encGen.open(out, compressedData.length); + + encOut.write(compressedData); + encOut.close(); + + if (armor) + { + out.close(); + } + + return bOut.toByteArray(); + } + + private static byte[] compress(byte[] clearData, String fileName, int algorithm) throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(algorithm); + OutputStream cos = comData.open(bOut); // open it with the final destination + + PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); + + // we want to generate compressed data. This might be a user option later, + // in which case we would pass in bOut. + OutputStream pOut = lData.open(cos, // the compressed output stream + PGPLiteralData.BINARY, + fileName, // "filename" to store + clearData.length, // length of clear data + new Date() // current time + ); + + pOut.write(clearData); + pOut.close(); + + comData.close(); + + return bOut.toByteArray(); + } + + public static void main(String[] args) throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + String passPhrase = "Dick Beck"; + char[] passArray = passPhrase.toCharArray(); + + byte[] original = "Hello world".getBytes(); + System.out.println("Starting PGP test"); + byte[] encrypted = encrypt(original, passArray, "iway", PGPEncryptedDataGenerator.CAST5, true); + + System.out.println("\nencrypted data = '"+new String(encrypted)+"'"); + byte[] decrypted= decrypt(encrypted,passArray); + + System.out.println("\ndecrypted data = '"+new String(decrypted)+"'"); + + encrypted = encrypt(original, passArray, "iway", PGPEncryptedDataGenerator.AES_256, false); + + System.out.println("\nencrypted data = '"+new String(org.spongycastle.util.encoders.Hex.encode(encrypted))+"'"); + decrypted= decrypt(encrypted, passArray); + + System.out.println("\ndecrypted data = '"+new String(decrypted)+"'"); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java new file mode 100644 index 000000000..4082e2a66 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java @@ -0,0 +1,390 @@ +package org.spongycastle.openpgp.examples; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.SignatureException; +import java.util.Iterator; + +import org.spongycastle.bcpg.ArmoredInputStream; +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRingCollection; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; + +/** + * A simple utility class that creates clear signed files and verifies them. + * <p> + * To sign a file: ClearSignedFileProcessor -s fileName secretKey passPhrase.<br> + * <p> + * To decrypt: ClearSignedFileProcessor -v fileName signatureFile publicKeyFile. + */ +public class ClearSignedFileProcessor +{ + private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) + throws IOException + { + bOut.reset(); + + int lookAhead = -1; + int ch; + + while ((ch = fIn.read()) >= 0) + { + bOut.write(ch); + if (ch == '\r' || ch == '\n') + { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } + + return lookAhead; + } + + private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) + throws IOException + { + bOut.reset(); + + int ch = lookAhead; + + do + { + bOut.write(ch); + if (ch == '\r' || ch == '\n') + { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } + while ((ch = fIn.read()) >= 0); + + if (ch < 0) + { + lookAhead = -1; + } + + return lookAhead; + } + + private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) + throws IOException + { + int lookAhead = fIn.read(); + + if (lastCh == '\r' && lookAhead == '\n') + { + bOut.write(lookAhead); + lookAhead = fIn.read(); + } + + return lookAhead; + } + + /* + * verify a clear text signed file + */ + private static void verifyFile( + InputStream in, + InputStream keyIn, + String resultName) + throws Exception + { + ArmoredInputStream aIn = new ArmoredInputStream(in); + OutputStream out = new BufferedOutputStream(new FileOutputStream(resultName)); + + + + // + // write out signed section using the local line separator. + // note: trailing white space needs to be removed from the end of + // each line RFC 4880 Section 7.1 + // + ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); + int lookAhead = readInputLine(lineOut, aIn); + byte[] lineSep = getLineSeparator(); + + if (lookAhead != -1 && aIn.isClearText()) + { + byte[] line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); + out.write(lineSep); + + while (lookAhead != -1 && aIn.isClearText()) + { + lookAhead = readInputLine(lineOut, lookAhead, aIn); + + line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); + out.write(lineSep); + } + } + + out.close(); + + PGPPublicKeyRingCollection pgpRings = new PGPPublicKeyRingCollection(keyIn); + + PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); + PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject(); + PGPSignature sig = p3.get(0); + + PGPPublicKey publicKey = pgpRings.getPublicKey(sig.getKeyID()); + sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("SC"), publicKey); + + // + // read the input, making sure we ignore the last newline. + // + + InputStream sigIn = new BufferedInputStream(new FileInputStream(resultName)); + + lookAhead = readInputLine(lineOut, sigIn); + + processLine(sig, lineOut.toByteArray()); + + if (lookAhead != -1) + { + do + { + lookAhead = readInputLine(lineOut, lookAhead, sigIn); + + sig.update((byte)'\r'); + sig.update((byte)'\n'); + + processLine(sig, lineOut.toByteArray()); + } + while (lookAhead != -1); + } + + sigIn.close(); + + if (sig.verify()) + { + System.out.println("signature verified."); + } + else + { + System.out.println("signature verification failed."); + } + } + + private static byte[] getLineSeparator() + { + String nl = System.getProperty("line.separator"); + byte[] nlBytes = new byte[nl.length()]; + + for (int i = 0; i != nlBytes.length; i++) + { + nlBytes[i] = (byte)nl.charAt(i); + } + + return nlBytes; + } + + /* + * create a clear text signed file. + */ + private static void signFile( + String fileName, + InputStream keyIn, + OutputStream out, + char[] pass, + String digestName) + throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException + { + int digest; + + if (digestName.equals("SHA256")) + { + digest = PGPUtil.SHA256; + } + else if (digestName.equals("SHA384")) + { + digest = PGPUtil.SHA384; + } + else if (digestName.equals("SHA512")) + { + digest = PGPUtil.SHA512; + } + else if (digestName.equals("MD5")) + { + digest = PGPUtil.MD5; + } + else if (digestName.equals("RIPEMD160")) + { + digest = PGPUtil.RIPEMD160; + } + else + { + digest = PGPUtil.SHA1; + } + + PGPSecretKey pgpSecKey = PGPExampleUtil.readSecretKey(keyIn); + PGPPrivateKey pgpPrivKey = pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass)); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSecKey.getPublicKey().getAlgorithm(), digest).setProvider("SC")); + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + + sGen.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey); + + Iterator it = pgpSecKey.getPublicKey().getUserIDs(); + if (it.hasNext()) + { + spGen.setSignerUserID(false, (String)it.next()); + sGen.setHashedSubpackets(spGen.generate()); + } + + InputStream fIn = new BufferedInputStream(new FileInputStream(fileName)); + ArmoredOutputStream aOut = new ArmoredOutputStream(out); + + aOut.beginClearText(digest); + + // + // note the last \n/\r/\r\n in the file is ignored + // + ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); + int lookAhead = readInputLine(lineOut, fIn); + + processLine(aOut, sGen, lineOut.toByteArray()); + + if (lookAhead != -1) + { + do + { + lookAhead = readInputLine(lineOut, lookAhead, fIn); + + sGen.update((byte)'\r'); + sGen.update((byte)'\n'); + + processLine(aOut, sGen, lineOut.toByteArray()); + } + while (lookAhead != -1); + } + + fIn.close(); + + aOut.endClearText(); + + BCPGOutputStream bOut = new BCPGOutputStream(aOut); + + sGen.generate().encode(bOut); + + aOut.close(); + } + + private static void processLine(PGPSignature sig, byte[] line) + throws SignatureException, IOException + { + int length = getLengthWithoutWhiteSpace(line); + if (length > 0) + { + sig.update(line, 0, length); + } + } + + private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line) + throws SignatureException, IOException + { + // note: trailing white space needs to be removed from the end of + // each line for signature calculation RFC 4880 Section 7.1 + int length = getLengthWithoutWhiteSpace(line); + if (length > 0) + { + sGen.update(line, 0, length); + } + + aOut.write(line, 0, line.length); + } + + private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) + { + int end = line.length - 1; + + while (end >= 0 && isWhiteSpace(line[end])) + { + end--; + } + + return end + 1; + } + + private static boolean isLineEnding(byte b) + { + return b == '\r' || b == '\n'; + } + + private static int getLengthWithoutWhiteSpace(byte[] line) + { + int end = line.length - 1; + + while (end >= 0 && isWhiteSpace(line[end])) + { + end--; + } + + return end + 1; + } + + private static boolean isWhiteSpace(byte b) + { + return isLineEnding(b) || b == '\t' || b == ' '; + } + + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + if (args[0].equals("-s")) + { + InputStream keyIn = PGPUtil.getDecoderStream(new FileInputStream(args[2])); + FileOutputStream out = new FileOutputStream(args[1] + ".asc"); + + if (args.length == 4) + { + signFile(args[1], keyIn, out, args[3].toCharArray(), "SHA1"); + } + else + { + signFile(args[1], keyIn, out, args[3].toCharArray(), args[4]); + } + } + else if (args[0].equals("-v")) + { + if (args[1].indexOf(".asc") < 0) + { + System.err.println("file needs to end in \".asc\""); + System.exit(1); + } + FileInputStream in = new FileInputStream(args[1]); + InputStream keyIn = PGPUtil.getDecoderStream(new FileInputStream(args[2])); + + verifyFile(in, keyIn, args[1].substring(0, args[1].length() - 4)); + } + else + { + System.err.println("usage: ClearSignedFileProcessor [-s file keyfile passPhrase]|[-v sigFile keyFile]"); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java new file mode 100644 index 000000000..e67e6f221 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java @@ -0,0 +1,139 @@ +package org.spongycastle.openpgp.examples; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.SignatureException; +import java.util.Date; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.jce.spec.ElGamalParameterSpec; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyPair; +import org.spongycastle.openpgp.PGPKeyRingGenerator; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; + +/** + * A simple utility class that generates a public/secret keyring containing a DSA signing + * key and an El Gamal key for encryption. + * <p> + * usage: DSAElGamalKeyRingGenerator [-a] identity passPhrase + * <p> + * Where identity is the name to be associated with the public key. The keys are placed + * in the files pub.[asc|bpg] and secret.[asc|bpg]. + * <p> + * <b>Note</b>: this example encrypts the secret key using AES_256, many PGP products still + * do not support this, if you are having problems importing keys try changing the algorithm + * id to PGPEncryptedData.CAST5. CAST5 is more widely supported. + */ +public class DSAElGamalKeyRingGenerator +{ + private static void exportKeyPair( + OutputStream secretOut, + OutputStream publicOut, + KeyPair dsaKp, + KeyPair elgKp, + String identity, + char[] passPhrase, + boolean armor) + throws IOException, InvalidKeyException, NoSuchProviderException, SignatureException, PGPException + { + if (armor) + { + secretOut = new ArmoredOutputStream(secretOut); + } + + PGPKeyPair dsaKeyPair = new JcaPGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date()); + PGPKeyPair elgKeyPair = new JcaPGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date()); + PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1); + PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair, + identity, sha1Calc, null, null, new JcaPGPContentSignerBuilder(dsaKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha1Calc).setProvider("SC").build(passPhrase)); + + keyRingGen.addSubKey(elgKeyPair); + + keyRingGen.generateSecretKeyRing().encode(secretOut); + + secretOut.close(); + + if (armor) + { + publicOut = new ArmoredOutputStream(publicOut); + } + + keyRingGen.generatePublicKeyRing().encode(publicOut); + + publicOut.close(); + } + + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + if (args.length < 2) + { + System.out.println("DSAElGamalKeyRingGenerator [-a] identity passPhrase"); + System.exit(0); + } + + KeyPairGenerator dsaKpg = KeyPairGenerator.getInstance("DSA", "SC"); + + dsaKpg.initialize(1024); + + // + // this takes a while as the key generator has to generate some DSA params + // before it generates the key. + // + KeyPair dsaKp = dsaKpg.generateKeyPair(); + + KeyPairGenerator elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "SC"); + BigInteger g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16); + BigInteger p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16); + + ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); + + elgKpg.initialize(elParams); + + // + // this is quicker because we are using pregenerated parameters. + // + KeyPair elgKp = elgKpg.generateKeyPair(); + + if (args[0].equals("-a")) + { + if (args.length < 3) + { + System.out.println("DSAElGamalKeyRingGenerator [-a] identity passPhrase"); + System.exit(0); + } + + FileOutputStream out1 = new FileOutputStream("secret.asc"); + FileOutputStream out2 = new FileOutputStream("pub.asc"); + + exportKeyPair(out1, out2, dsaKp, elgKp, args[1], args[2].toCharArray(), true); + } + else + { + FileOutputStream out1 = new FileOutputStream("secret.bpg"); + FileOutputStream out2 = new FileOutputStream("pub.bpg"); + + exportKeyPair(out1, out2, dsaKp, elgKp, args[0], args[1].toCharArray(), false); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DetachedSignatureProcessor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DetachedSignatureProcessor.java new file mode 100644 index 000000000..5c25d5e35 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DetachedSignatureProcessor.java @@ -0,0 +1,198 @@ +package org.spongycastle.openpgp.examples; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.Security; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRingCollection; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; + +/** + * A simple utility class that creates seperate signatures for files and verifies them. + * <p> + * To sign a file: DetachedSignatureProcessor -s [-a] fileName secretKey passPhrase.<br> + * If -a is specified the output file will be "ascii-armored". + * <p> + * To decrypt: DetachedSignatureProcessor -v fileName signatureFile publicKeyFile. + * <p> + * Note: this example will silently overwrite files. + * It also expects that a single pass phrase + * will have been used. + */ +public class DetachedSignatureProcessor +{ + private static void verifySignature( + String fileName, + String inputFileName, + String keyFileName) + throws GeneralSecurityException, IOException, PGPException + { + InputStream in = new BufferedInputStream(new FileInputStream(inputFileName)); + InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName)); + + verifySignature(fileName, in, keyIn); + + keyIn.close(); + in.close(); + } + + /* + * verify the signature in in against the file fileName. + */ + private static void verifySignature( + String fileName, + InputStream in, + InputStream keyIn) + throws GeneralSecurityException, IOException, PGPException + { + in = PGPUtil.getDecoderStream(in); + + PGPObjectFactory pgpFact = new PGPObjectFactory(in); + PGPSignatureList p3; + + Object o = pgpFact.nextObject(); + if (o instanceof PGPCompressedData) + { + PGPCompressedData c1 = (PGPCompressedData)o; + + pgpFact = new PGPObjectFactory(c1.getDataStream()); + + p3 = (PGPSignatureList)pgpFact.nextObject(); + } + else + { + p3 = (PGPSignatureList)o; + } + + PGPPublicKeyRingCollection pgpPubRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn)); + + + InputStream dIn = new BufferedInputStream(new FileInputStream(fileName)); + + PGPSignature sig = p3.get(0); + PGPPublicKey key = pgpPubRingCollection.getPublicKey(sig.getKeyID()); + + sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider("SC"), key); + + int ch; + while ((ch = dIn.read()) >= 0) + { + sig.update((byte)ch); + } + + dIn.close(); + + if (sig.verify()) + { + System.out.println("signature verified."); + } + else + { + System.out.println("signature verification failed."); + } + } + + private static void createSignature( + String inputFileName, + String keyFileName, + String outputFileName, + char[] pass, + boolean armor) + throws GeneralSecurityException, IOException, PGPException + { + InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName)); + OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName)); + + createSignature(inputFileName, keyIn, out, pass, armor); + + out.close(); + keyIn.close(); + } + + private static void createSignature( + String fileName, + InputStream keyIn, + OutputStream out, + char[] pass, + boolean armor) + throws GeneralSecurityException, IOException, PGPException + { + if (armor) + { + out = new ArmoredOutputStream(out); + } + + PGPSecretKey pgpSec = PGPExampleUtil.readSecretKey(keyIn); + PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass)); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("SC")); + + sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey); + + BCPGOutputStream bOut = new BCPGOutputStream(out); + + InputStream fIn = new BufferedInputStream(new FileInputStream(fileName)); + + int ch; + while ((ch = fIn.read()) >= 0) + { + sGen.update((byte)ch); + } + + fIn.close(); + + sGen.generate().encode(bOut); + + if (armor) + { + out.close(); + } + } + + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + if (args[0].equals("-s")) + { + if (args[1].equals("-a")) + { + createSignature(args[2], args[3], args[2] + ".asc", args[4].toCharArray(), true); + } + else + { + createSignature(args[1], args[2], args[1] + ".bpg", args[3].toCharArray(), false); + } + } + else if (args[0].equals("-v")) + { + verifySignature(args[1], args[2], args[3]); + } + else + { + System.err.println("usage: DetachedSignatureProcessor [-s [-a] file keyfile passPhrase]|[-v file sigFile keyFile]"); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DirectKeySignature.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DirectKeySignature.java new file mode 100644 index 000000000..8efd01e3d --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/DirectKeySignature.java @@ -0,0 +1,135 @@ +package org.spongycastle.openpgp.examples; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.security.Security; +import java.util.Iterator; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.sig.NotationData; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; + +/** + * A simple utility class that directly signs a public key and writes the signed key to "SignedKey.asc" in + * the current working directory. + * <p> + * To sign a key: DirectKeySignature secretKeyFile secretKeyPass publicKeyFile(key to be signed) NotationName NotationValue.<br/> + * </p><p> + * To display a NotationData packet from a publicKey previously signed: DirectKeySignature signedPublicKeyFile.<br/> + * </p><p> + * <b>Note</b>: this example will silently overwrite files, nor does it pay any attention to + * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase + * will have been used. + * </p> + */ +public class DirectKeySignature +{ + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + if (args.length == 1) + { + PGPPublicKeyRing ring = new PGPPublicKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[0])), new JcaKeyFingerprintCalculator()); + PGPPublicKey key = ring.getPublicKey(); + + // iterate through all direct key signautures and look for NotationData subpackets + Iterator iter = key.getSignaturesOfType(PGPSignature.DIRECT_KEY); + while(iter.hasNext()) + { + PGPSignature sig = (PGPSignature)iter.next(); + + System.out.println("Signature date is: " + sig.getHashedSubPackets().getSignatureCreationTime()); + + NotationData[] data = sig.getHashedSubPackets().getNotationDataOccurences();//.getSubpacket(SignatureSubpacketTags.NOTATION_DATA); + + for (int i = 0; i < data.length; i++) + { + System.out.println("Found Notaion named '"+data[i].getNotationName()+"' with content '"+data[i].getNotationValue()+"'."); + } + } + } + else if (args.length == 5) + { + // gather command line arguments + PGPSecretKeyRing secRing = new PGPSecretKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[0])), new JcaKeyFingerprintCalculator()); + String secretKeyPass = args[1]; + PGPPublicKeyRing ring = new PGPPublicKeyRing(PGPUtil.getDecoderStream(new FileInputStream(args[2])), new JcaKeyFingerprintCalculator()); + String notationName = args[3]; + String notationValue = args[4]; + + // create the signed keyRing + PGPPublicKeyRing sRing = new PGPPublicKeyRing(new ByteArrayInputStream(signPublicKey(secRing.getSecretKey(), secretKeyPass, ring.getPublicKey(), notationName, notationValue, true)), new JcaKeyFingerprintCalculator()); + ring = sRing; + + // write the created keyRing to file + ArmoredOutputStream out = new ArmoredOutputStream(new FileOutputStream("SignedKey.asc")); + sRing.encode(out); + out.flush(); + out.close(); + } + else + { + System.err.println("usage: DirectKeySignature secretKeyFile secretKeyPass publicKeyFile(key to be signed) NotationName NotationValue"); + System.err.println("or: DirectKeySignature signedPublicKeyFile"); + + } + } + + private static byte[] signPublicKey(PGPSecretKey secretKey, String secretKeyPass, PGPPublicKey keyToBeSigned, String notationName, String notationValue, boolean armor) throws Exception + { + OutputStream out = new ByteArrayOutputStream(); + + if (armor) + { + out = new ArmoredOutputStream(out); + } + + PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(secretKeyPass.toCharArray())); + + PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("SC")); + + sGen.init(PGPSignature.DIRECT_KEY, pgpPrivKey); + + BCPGOutputStream bOut = new BCPGOutputStream(out); + + sGen.generateOnePassVersion(false).encode(bOut); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + + boolean isHumanReadable = true; + + spGen.setNotationData(true, isHumanReadable, notationName, notationValue); + + PGPSignatureSubpacketVector packetVector = spGen.generate(); + sGen.setHashedSubpackets(packetVector); + + bOut.flush(); + + if (armor) + { + out.close(); + } + + return PGPPublicKey.addCertification(keyToBeSigned, sGen.generate()).getEncoded(); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedFileProcessor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedFileProcessor.java new file mode 100644 index 000000000..72f97fe82 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedFileProcessor.java @@ -0,0 +1,279 @@ +package org.spongycastle.openpgp.examples; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Iterator; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPOnePassSignatureList; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.PGPSecretKeyRingCollection; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.spongycastle.util.io.Streams; + +/** + * A simple utility class that encrypts/decrypts public key based + * encryption files. + * <p> + * To encrypt a file: KeyBasedFileProcessor -e [-a|-ai] fileName publicKeyFile.<br> + * If -a is specified the output file will be "ascii-armored". + * If -i is specified the output file will be have integrity checking added. + * <p> + * To decrypt: KeyBasedFileProcessor -d fileName secretKeyFile passPhrase. + * <p> + * Note 1: this example will silently overwrite files, nor does it pay any attention to + * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase + * will have been used. + * <p> + * Note 2: if an empty file name has been specified in the literal data object contained in the + * encrypted packet a file with the name filename.out will be generated in the current working directory. + */ +public class KeyBasedFileProcessor +{ + private static void decryptFile( + String inputFileName, + String keyFileName, + char[] passwd, + String defaultFileName) + throws IOException, NoSuchProviderException + { + InputStream in = new BufferedInputStream(new FileInputStream(inputFileName)); + InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName)); + decryptFile(in, keyIn, passwd, defaultFileName); + keyIn.close(); + in.close(); + } + + /** + * decrypt the passed in message stream + */ + private static void decryptFile( + InputStream in, + InputStream keyIn, + char[] passwd, + String defaultFileName) + throws IOException, NoSuchProviderException + { + in = PGPUtil.getDecoderStream(in); + + try + { + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + + Object o = pgpF.nextObject(); + // + // the first object might be a PGP marker packet. + // + if (o instanceof PGPEncryptedDataList) + { + enc = (PGPEncryptedDataList)o; + } + else + { + enc = (PGPEncryptedDataList)pgpF.nextObject(); + } + + // + // find the secret key + // + Iterator it = enc.getEncryptedDataObjects(); + PGPPrivateKey sKey = null; + PGPPublicKeyEncryptedData pbe = null; + PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection( + PGPUtil.getDecoderStream(keyIn)); + + while (sKey == null && it.hasNext()) + { + pbe = (PGPPublicKeyEncryptedData)it.next(); + + sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd); + } + + if (sKey == null) + { + throw new IllegalArgumentException("secret key for message not found."); + } + + InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("SC").build(sKey)); + + PGPObjectFactory plainFact = new PGPObjectFactory(clear); + + Object message = plainFact.nextObject(); + + if (message instanceof PGPCompressedData) + { + PGPCompressedData cData = (PGPCompressedData)message; + PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream()); + + message = pgpFact.nextObject(); + } + + if (message instanceof PGPLiteralData) + { + PGPLiteralData ld = (PGPLiteralData)message; + + String outFileName = ld.getFileName(); + if (outFileName.length() == 0) + { + outFileName = defaultFileName; + } + + InputStream unc = ld.getInputStream(); + OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName)); + + Streams.pipeAll(unc, fOut); + + fOut.close(); + } + else if (message instanceof PGPOnePassSignatureList) + { + throw new PGPException("encrypted message contains a signed message - not literal data."); + } + else + { + throw new PGPException("message is not a simple encrypted file - type unknown."); + } + + if (pbe.isIntegrityProtected()) + { + if (!pbe.verify()) + { + System.err.println("message failed integrity check"); + } + else + { + System.err.println("message integrity check passed"); + } + } + else + { + System.err.println("no message integrity check"); + } + } + catch (PGPException e) + { + System.err.println(e); + if (e.getUnderlyingException() != null) + { + e.getUnderlyingException().printStackTrace(); + } + } + } + + private static void encryptFile( + String outputFileName, + String inputFileName, + String encKeyFileName, + boolean armor, + boolean withIntegrityCheck) + throws IOException, NoSuchProviderException, PGPException + { + OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName)); + PGPPublicKey encKey = PGPExampleUtil.readPublicKey(encKeyFileName); + encryptFile(out, inputFileName, encKey, armor, withIntegrityCheck); + out.close(); + } + + private static void encryptFile( + OutputStream out, + String fileName, + PGPPublicKey encKey, + boolean armor, + boolean withIntegrityCheck) + throws IOException, NoSuchProviderException + { + if (armor) + { + out = new ArmoredOutputStream(out); + } + + try + { + byte[] bytes = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP); + + PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator( + new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("SC")); + + encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("SC")); + + OutputStream cOut = encGen.open(out, bytes.length); + + cOut.write(bytes); + cOut.close(); + + if (armor) + { + out.close(); + } + } + catch (PGPException e) + { + System.err.println(e); + if (e.getUnderlyingException() != null) + { + e.getUnderlyingException().printStackTrace(); + } + } + } + + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + if (args.length == 0) + { + System.err.println("usage: KeyBasedFileProcessor -e|-d [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]"); + return; + } + + if (args[0].equals("-e")) + { + if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia")) + { + encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0)); + } + else if (args[1].equals("-i")) + { + encryptFile(args[2] + ".bpg", args[2], args[3], false, true); + } + else + { + encryptFile(args[1] + ".bpg", args[1], args[2], false, false); + } + } + else if (args[0].equals("-d")) + { + decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out"); + } + else + { + System.err.println("usage: KeyBasedFileProcessor -d|-e [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]"); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedLargeFileProcessor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedLargeFileProcessor.java new file mode 100644 index 000000000..8bcc9049c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/KeyBasedLargeFileProcessor.java @@ -0,0 +1,283 @@ +package org.spongycastle.openpgp.examples; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Iterator; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPOnePassSignatureList; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.PGPSecretKeyRingCollection; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.spongycastle.util.io.Streams; + +/** + * A simple utility class that encrypts/decrypts public key based + * encryption large files. + * <p> + * To encrypt a file: KeyBasedLargeFileProcessor -e [-a|-ai] fileName publicKeyFile.<br> + * If -a is specified the output file will be "ascii-armored". + * If -i is specified the output file will be have integrity checking added. + * <p> + * To decrypt: KeyBasedLargeFileProcessor -d fileName secretKeyFile passPhrase. + * <p> + * Note 1: this example will silently overwrite files, nor does it pay any attention to + * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase + * will have been used. + * <p> + * Note 2: this example generates partial packets to encode the file, the output it generates + * will not be readable by older PGP products or products that don't support partial packet + * encoding. + * <p> + * Note 3: if an empty file name has been specified in the literal data object contained in the + * encrypted packet a file with the name filename.out will be generated in the current working directory. + */ +public class KeyBasedLargeFileProcessor +{ + private static void decryptFile( + String inputFileName, + String keyFileName, + char[] passwd, + String defaultFileName) + throws IOException, NoSuchProviderException + { + InputStream in = new BufferedInputStream(new FileInputStream(inputFileName)); + InputStream keyIn = new BufferedInputStream(new FileInputStream(keyFileName)); + decryptFile(in, keyIn, passwd, defaultFileName); + keyIn.close(); + in.close(); + } + + /** + * decrypt the passed in message stream + */ + private static void decryptFile( + InputStream in, + InputStream keyIn, + char[] passwd, + String defaultFileName) + throws IOException, NoSuchProviderException + { + in = PGPUtil.getDecoderStream(in); + + try + { + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + + Object o = pgpF.nextObject(); + // + // the first object might be a PGP marker packet. + // + if (o instanceof PGPEncryptedDataList) + { + enc = (PGPEncryptedDataList)o; + } + else + { + enc = (PGPEncryptedDataList)pgpF.nextObject(); + } + + // + // find the secret key + // + Iterator it = enc.getEncryptedDataObjects(); + PGPPrivateKey sKey = null; + PGPPublicKeyEncryptedData pbe = null; + PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection( + PGPUtil.getDecoderStream(keyIn)); + + while (sKey == null && it.hasNext()) + { + pbe = (PGPPublicKeyEncryptedData)it.next(); + + sKey = PGPExampleUtil.findSecretKey(pgpSec, pbe.getKeyID(), passwd); + } + + if (sKey == null) + { + throw new IllegalArgumentException("secret key for message not found."); + } + + InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("SC").build(sKey)); + + PGPObjectFactory plainFact = new PGPObjectFactory(clear); + + PGPCompressedData cData = (PGPCompressedData)plainFact.nextObject(); + + InputStream compressedStream = new BufferedInputStream(cData.getDataStream()); + PGPObjectFactory pgpFact = new PGPObjectFactory(compressedStream); + + Object message = pgpFact.nextObject(); + + if (message instanceof PGPLiteralData) + { + PGPLiteralData ld = (PGPLiteralData)message; + + String outFileName = ld.getFileName(); + if (outFileName.length() == 0) + { + outFileName = defaultFileName; + } + + InputStream unc = ld.getInputStream(); + OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName)); + + Streams.pipeAll(unc, fOut); + + fOut.close(); + } + else if (message instanceof PGPOnePassSignatureList) + { + throw new PGPException("encrypted message contains a signed message - not literal data."); + } + else + { + throw new PGPException("message is not a simple encrypted file - type unknown."); + } + + if (pbe.isIntegrityProtected()) + { + if (!pbe.verify()) + { + System.err.println("message failed integrity check"); + } + else + { + System.err.println("message integrity check passed"); + } + } + else + { + System.err.println("no message integrity check"); + } + } + catch (PGPException e) + { + System.err.println(e); + if (e.getUnderlyingException() != null) + { + e.getUnderlyingException().printStackTrace(); + } + } + } + + private static void encryptFile( + String outputFileName, + String inputFileName, + String encKeyFileName, + boolean armor, + boolean withIntegrityCheck) + throws IOException, NoSuchProviderException, PGPException + { + OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName)); + PGPPublicKey encKey = PGPExampleUtil.readPublicKey(encKeyFileName); + encryptFile(out, inputFileName, encKey, armor, withIntegrityCheck); + out.close(); + } + + private static void encryptFile( + OutputStream out, + String fileName, + PGPPublicKey encKey, + boolean armor, + boolean withIntegrityCheck) + throws IOException, NoSuchProviderException + { + if (armor) + { + out = new ArmoredOutputStream(out); + } + + try + { + PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("SC")); + + cPk.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("SC")); + + OutputStream cOut = cPk.open(out, new byte[1 << 16]); + + PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator( + PGPCompressedData.ZIP); + + PGPUtil.writeFileToLiteralData(comData.open(cOut), PGPLiteralData.BINARY, new File(fileName), new byte[1 << 16]); + + comData.close(); + + cOut.close(); + + if (armor) + { + out.close(); + } + } + catch (PGPException e) + { + System.err.println(e); + if (e.getUnderlyingException() != null) + { + e.getUnderlyingException().printStackTrace(); + } + } + } + + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + if (args.length == 0) + { + System.err.println("usage: KeyBasedLargeFileProcessor -e|-d [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]"); + return; + } + + if (args[0].equals("-e")) + { + if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia")) + { + encryptFile(args[2] + ".asc", args[2], args[3], true, (args[1].indexOf('i') > 0)); + } + else if (args[1].equals("-i")) + { + encryptFile(args[2] + ".bpg", args[2], args[3], false, true); + } + else + { + encryptFile(args[1] + ".bpg", args[1], args[2], false, false); + } + } + else if (args[0].equals("-d")) + { + decryptFile(args[1], args[2], args[3].toCharArray(), new File(args[1]).getName() + ".out"); + } + else + { + System.err.println("usage: KeyBasedLargeFileProcessor -d|-e [-a|ai] file [secretKeyFile passPhrase|pubKeyFile]"); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PBEFileProcessor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PBEFileProcessor.java new file mode 100644 index 000000000..ee0b14d97 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PBEFileProcessor.java @@ -0,0 +1,214 @@ +package org.spongycastle.openpgp.examples; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPPBEEncryptedData; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.spongycastle.util.io.Streams; + +/** + * A simple utility class that encrypts/decrypts password based + * encryption files. + * <p> + * To encrypt a file: PBEFileProcessor -e [-ai] fileName passPhrase.<br> + * If -a is specified the output file will be "ascii-armored".<br> + * If -i is specified the output file will be "integrity protected". + * <p> + * To decrypt: PBEFileProcessor -d fileName passPhrase. + * <p> + * Note: this example will silently overwrite files, nor does it pay any attention to + * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase + * will have been used. + */ +public class PBEFileProcessor +{ + private static void decryptFile(String inputFileName, char[] passPhrase) + throws IOException, NoSuchProviderException, PGPException + { + InputStream in = new BufferedInputStream(new FileInputStream(inputFileName)); + decryptFile(in, passPhrase); + in.close(); + } + + /* + * decrypt the passed in message stream + */ + private static void decryptFile( + InputStream in, + char[] passPhrase) + throws IOException, NoSuchProviderException, PGPException + { + in = PGPUtil.getDecoderStream(in); + + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + // + // the first object might be a PGP marker packet. + // + if (o instanceof PGPEncryptedDataList) + { + enc = (PGPEncryptedDataList)o; + } + else + { + enc = (PGPEncryptedDataList)pgpF.nextObject(); + } + + PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0); + + InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("SC").build()).setProvider("SC").build(passPhrase)); + + PGPObjectFactory pgpFact = new PGPObjectFactory(clear); + + // + // if we're trying to read a file generated by someone other than us + // the data might not be compressed, so we check the return type from + // the factory and behave accordingly. + // + o = pgpFact.nextObject(); + if (o instanceof PGPCompressedData) + { + PGPCompressedData cData = (PGPCompressedData)o; + + pgpFact = new PGPObjectFactory(cData.getDataStream()); + + o = pgpFact.nextObject(); + } + + PGPLiteralData ld = (PGPLiteralData)o; + InputStream unc = ld.getInputStream(); + + OutputStream fOut = new BufferedOutputStream(new FileOutputStream(ld.getFileName())); + + Streams.pipeAll(unc, fOut); + + fOut.close(); + + if (pbe.isIntegrityProtected()) + { + if (!pbe.verify()) + { + System.err.println("message failed integrity check"); + } + else + { + System.err.println("message integrity check passed"); + } + } + else + { + System.err.println("no message integrity check"); + } + } + + private static void encryptFile( + String outputFileName, + String inputFileName, + char[] passPhrase, + boolean armor, + boolean withIntegrityCheck) + throws IOException, NoSuchProviderException + { + OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName)); + encryptFile(out, inputFileName, passPhrase, armor, withIntegrityCheck); + out.close(); + } + + private static void encryptFile( + OutputStream out, + String fileName, + char[] passPhrase, + boolean armor, + boolean withIntegrityCheck) + throws IOException, NoSuchProviderException + { + if (armor) + { + out = new ArmoredOutputStream(out); + } + + try + { + byte[] compressedData = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP); + + PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5) + .setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("SC")); + + encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase).setProvider("SC")); + + OutputStream encOut = encGen.open(out, compressedData.length); + + encOut.write(compressedData); + encOut.close(); + + if (armor) + { + out.close(); + } + } + catch (PGPException e) + { + System.err.println(e); + if (e.getUnderlyingException() != null) + { + e.getUnderlyingException().printStackTrace(); + } + } + } + + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + if (args[0].equals("-e")) + { + if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia")) + { + encryptFile(args[2] + ".asc", args[2], args[3].toCharArray(), true, (args[1].indexOf('i') > 0)); + } + else if (args[1].equals("-i")) + { + encryptFile(args[2] + ".bpg", args[2], args[3].toCharArray(), false, true); + } + else + { + encryptFile(args[1] + ".bpg", args[1], args[2].toCharArray(), false, false); + } + } + else if (args[0].equals("-d")) + { + decryptFile(args[1], args[2].toCharArray()); + } + else + { + System.err.println("usage: PBEFileProcessor -e [-ai]|-d file passPhrase"); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PGPExampleUtil.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PGPExampleUtil.java new file mode 100644 index 000000000..1f075ce97 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PGPExampleUtil.java @@ -0,0 +1,154 @@ +package org.spongycastle.openpgp.examples; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchProviderException; +import java.util.Iterator; + +import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPPublicKeyRingCollection; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSecretKeyRingCollection; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; + +class PGPExampleUtil +{ + static byte[] compressFile(String fileName, int algorithm) throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(algorithm); + PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, + new File(fileName)); + comData.close(); + return bOut.toByteArray(); + } + + /** + * Search a secret key ring collection for a secret key corresponding to keyID if it + * exists. + * + * @param pgpSec a secret key ring collection. + * @param keyID keyID we want. + * @param pass passphrase to decrypt secret key with. + * @return the private key. + * @throws PGPException + * @throws NoSuchProviderException + */ + static PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass) + throws PGPException, NoSuchProviderException + { + PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID); + + if (pgpSecKey == null) + { + return null; + } + + return pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass)); + } + + static PGPPublicKey readPublicKey(String fileName) throws IOException, PGPException + { + InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName)); + PGPPublicKey pubKey = readPublicKey(keyIn); + keyIn.close(); + return pubKey; + } + + /** + * A simple routine that opens a key ring file and loads the first available key + * suitable for encryption. + * + * @param input data stream containing the public key data + * @return the first public key found. + * @throws IOException + * @throws PGPException + */ + static PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException + { + PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( + PGPUtil.getDecoderStream(input)); + + // + // we just loop through the collection till we find a key suitable for encryption, in the real + // world you would probably want to be a bit smarter about this. + // + + Iterator keyRingIter = pgpPub.getKeyRings(); + while (keyRingIter.hasNext()) + { + PGPPublicKeyRing keyRing = (PGPPublicKeyRing)keyRingIter.next(); + + Iterator keyIter = keyRing.getPublicKeys(); + while (keyIter.hasNext()) + { + PGPPublicKey key = (PGPPublicKey)keyIter.next(); + + if (key.isEncryptionKey()) + { + return key; + } + } + } + + throw new IllegalArgumentException("Can't find encryption key in key ring."); + } + + static PGPSecretKey readSecretKey(String fileName) throws IOException, PGPException + { + InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName)); + PGPSecretKey secKey = readSecretKey(keyIn); + keyIn.close(); + return secKey; + } + + /** + * A simple routine that opens a key ring file and loads the first available key + * suitable for signature generation. + * + * @param input stream to read the secret key ring collection from. + * @return a secret key. + * @throws IOException on a problem with using the input stream. + * @throws PGPException if there is an issue parsing the input stream. + */ + static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException + { + PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection( + PGPUtil.getDecoderStream(input)); + + // + // we just loop through the collection till we find a key suitable for encryption, in the real + // world you would probably want to be a bit smarter about this. + // + + Iterator keyRingIter = pgpSec.getKeyRings(); + while (keyRingIter.hasNext()) + { + PGPSecretKeyRing keyRing = (PGPSecretKeyRing)keyRingIter.next(); + + Iterator keyIter = keyRing.getSecretKeys(); + while (keyIter.hasNext()) + { + PGPSecretKey key = (PGPSecretKey)keyIter.next(); + + if (key.isSigningKey()) + { + return key; + } + } + } + + throw new IllegalArgumentException("Can't find signing key in key ring."); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PubringDump.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PubringDump.java new file mode 100644 index 000000000..5439502b2 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/PubringDump.java @@ -0,0 +1,102 @@ +package org.spongycastle.openpgp.examples; + +import java.io.*; + +import java.security.Security; +import java.util.Iterator; + +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.jce.provider.BouncyCastleProvider; + +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPPublicKeyRingCollection; +import org.spongycastle.openpgp.PGPUtil; + +import org.spongycastle.util.encoders.Hex; + +/** + * Basic class which just lists the contents of the public key file passed + * as an argument. If the file contains more than one "key ring" they are + * listed in the order found. + */ +public class PubringDump +{ + public static String getAlgorithm( + int algId) + { + switch (algId) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + return "RSA_GENERAL"; + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + return "RSA_ENCRYPT"; + case PublicKeyAlgorithmTags.RSA_SIGN: + return "RSA_SIGN"; + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + return "ELGAMAL_ENCRYPT"; + case PublicKeyAlgorithmTags.DSA: + return "DSA"; + case PublicKeyAlgorithmTags.EC: + return "EC"; + case PublicKeyAlgorithmTags.ECDSA: + return "ECDSA"; + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + return "ELGAMAL_GENERAL"; + case PublicKeyAlgorithmTags.DIFFIE_HELLMAN: + return "DIFFIE_HELLMAN"; + } + + return "unknown"; + } + + public static void main(String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + PGPUtil.setDefaultProvider("SC"); + + // + // Read the public key rings + // + PGPPublicKeyRingCollection pubRings = new PGPPublicKeyRingCollection( + PGPUtil.getDecoderStream(new FileInputStream(args[0]))); + + Iterator rIt = pubRings.getKeyRings(); + + while (rIt.hasNext()) + { + PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)rIt.next(); + + try + { + pgpPub.getPublicKey(); + } + catch (Exception e) + { + e.printStackTrace(); + continue; + } + + Iterator it = pgpPub.getPublicKeys(); + boolean first = true; + while (it.hasNext()) + { + PGPPublicKey pgpKey = (PGPPublicKey)it.next(); + + if (first) + { + System.out.println("Key ID: " + Long.toHexString(pgpKey.getKeyID())); + first = false; + } + else + { + System.out.println("Key ID: " + Long.toHexString(pgpKey.getKeyID()) + " (subkey)"); + } + System.out.println(" Algorithm: " + getAlgorithm(pgpKey.getAlgorithm())); + System.out.println(" Fingerprint: " + new String(Hex.encode(pgpKey.getFingerprint()))); + } + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/RSAKeyPairGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/RSAKeyPairGenerator.java new file mode 100644 index 000000000..99042fd38 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/RSAKeyPairGenerator.java @@ -0,0 +1,114 @@ +package org.spongycastle.openpgp.examples; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Security; +import java.security.SignatureException; +import java.util.Date; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyPair; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; + +/** + * A simple utility class that generates a RSA PGPPublicKey/PGPSecretKey pair. + * <p> + * usage: RSAKeyPairGenerator [-a] identity passPhrase + * <p> + * Where identity is the name to be associated with the public key. The keys are placed + * in the files pub.[asc|bpg] and secret.[asc|bpg]. + */ +public class RSAKeyPairGenerator +{ + private static void exportKeyPair( + OutputStream secretOut, + OutputStream publicOut, + PublicKey publicKey, + PrivateKey privateKey, + String identity, + char[] passPhrase, + boolean armor) + throws IOException, InvalidKeyException, NoSuchProviderException, SignatureException, PGPException + { + if (armor) + { + secretOut = new ArmoredOutputStream(secretOut); + } + + PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1); + PGPKeyPair keyPair = new PGPKeyPair(PGPPublicKey.RSA_GENERAL, publicKey, privateKey, new Date()); + PGPSecretKey secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, identity, sha1Calc, null, null, new JcaPGPContentSignerBuilder(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc).setProvider("SC").build(passPhrase)); + + secretKey.encode(secretOut); + + secretOut.close(); + + if (armor) + { + publicOut = new ArmoredOutputStream(publicOut); + } + + PGPPublicKey key = secretKey.getPublicKey(); + + key.encode(publicOut); + + publicOut.close(); + } + + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "SC"); + + kpg.initialize(1024); + + KeyPair kp = kpg.generateKeyPair(); + + if (args.length < 2) + { + System.out.println("RSAKeyPairGenerator [-a] identity passPhrase"); + System.exit(0); + } + + if (args[0].equals("-a")) + { + if (args.length < 3) + { + System.out.println("RSAKeyPairGenerator [-a] identity passPhrase"); + System.exit(0); + } + + FileOutputStream out1 = new FileOutputStream("secret.asc"); + FileOutputStream out2 = new FileOutputStream("pub.asc"); + + exportKeyPair(out1, out2, kp.getPublic(), kp.getPrivate(), args[1], args[2].toCharArray(), true); + } + else + { + FileOutputStream out1 = new FileOutputStream("secret.bpg"); + FileOutputStream out2 = new FileOutputStream("pub.bpg"); + + exportKeyPair(out1, out2, kp.getPublic(), kp.getPrivate(), args[0], args[1].toCharArray(), false); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/SignedFileProcessor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/SignedFileProcessor.java new file mode 100644 index 000000000..ad3311f1c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/examples/SignedFileProcessor.java @@ -0,0 +1,215 @@ +package org.spongycastle.openpgp.examples; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.SignatureException; +import java.util.Iterator; + +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPLiteralDataGenerator; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPOnePassSignature; +import org.spongycastle.openpgp.PGPOnePassSignatureList; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRingCollection; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; +import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; + +/** + * A simple utility class that signs and verifies files. + * <p> + * To sign a file: SignedFileProcessor -s [-a] fileName secretKey passPhrase.<br> + * If -a is specified the output file will be "ascii-armored". + * <p> + * To decrypt: SignedFileProcessor -v fileName publicKeyFile. + * <p> + * <b>Note</b>: this example will silently overwrite files, nor does it pay any attention to + * the specification of "_CONSOLE" in the filename. It also expects that a single pass phrase + * will have been used. + * <p> + * <b>Note</b>: the example also makes use of PGP compression. If you are having difficulty getting it + * to interoperate with other PGP programs try removing the use of compression first. + */ +public class SignedFileProcessor +{ + /* + * verify the passed in file as being correctly signed. + */ + private static void verifyFile( + InputStream in, + InputStream keyIn) + throws Exception + { + in = PGPUtil.getDecoderStream(in); + + PGPObjectFactory pgpFact = new PGPObjectFactory(in); + + PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject(); + + pgpFact = new PGPObjectFactory(c1.getDataStream()); + + PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject(); + + PGPOnePassSignature ops = p1.get(0); + + PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject(); + + InputStream dIn = p2.getInputStream(); + int ch; + PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn)); + + PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID()); + FileOutputStream out = new FileOutputStream(p2.getFileName()); + + ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("SC"), key); + + while ((ch = dIn.read()) >= 0) + { + ops.update((byte)ch); + out.write(ch); + } + + out.close(); + + PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject(); + + if (ops.verify(p3.get(0))) + { + System.out.println("signature verified."); + } + else + { + System.out.println("signature verification failed."); + } + } + + /** + * Generate an encapsulated signed file. + * + * @param fileName + * @param keyIn + * @param out + * @param pass + * @param armor + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws PGPException + * @throws SignatureException + */ + private static void signFile( + String fileName, + InputStream keyIn, + OutputStream out, + char[] pass, + boolean armor) + throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException + { + if (armor) + { + out = new ArmoredOutputStream(out); + } + + PGPSecretKey pgpSec = PGPExampleUtil.readSecretKey(keyIn); + PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("SC").build(pass)); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("SC")); + + sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey); + + Iterator it = pgpSec.getPublicKey().getUserIDs(); + if (it.hasNext()) + { + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + + spGen.setSignerUserID(false, (String)it.next()); + sGen.setHashedSubpackets(spGen.generate()); + } + + PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator( + PGPCompressedData.ZLIB); + + BCPGOutputStream bOut = new BCPGOutputStream(cGen.open(out)); + + sGen.generateOnePassVersion(false).encode(bOut); + + File file = new File(fileName); + PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator(); + OutputStream lOut = lGen.open(bOut, PGPLiteralData.BINARY, file); + FileInputStream fIn = new FileInputStream(file); + int ch; + + while ((ch = fIn.read()) >= 0) + { + lOut.write(ch); + sGen.update((byte)ch); + } + + lGen.close(); + + sGen.generate().encode(bOut); + + cGen.close(); + + if (armor) + { + out.close(); + } + } + + public static void main( + String[] args) + throws Exception + { + Security.addProvider(new BouncyCastleProvider()); + + if (args[0].equals("-s")) + { + if (args[1].equals("-a")) + { + FileInputStream keyIn = new FileInputStream(args[3]); + FileOutputStream out = new FileOutputStream(args[2] + ".asc"); + + signFile(args[2], keyIn, out, args[4].toCharArray(), true); + } + else + { + FileInputStream keyIn = new FileInputStream(args[2]); + FileOutputStream out = new FileOutputStream(args[1] + ".bpg"); + + signFile(args[1], keyIn, out, args[3].toCharArray(), false); + } + } + else if (args[0].equals("-v")) + { + FileInputStream in = new FileInputStream(args[1]); + FileInputStream keyIn = new FileInputStream(args[2]); + + verifyFile(in, keyIn); + } + else + { + System.err.println("usage: SignedFileProcessor -v|-s [-a] file keyfile [passPhrase]"); + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java new file mode 100644 index 000000000..a5a8a2696 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/KeyFingerPrintCalculator.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.openpgp.PGPException; + +public interface KeyFingerPrintCalculator +{ + byte[] calculateFingerprint(PublicKeyPacket publicPk) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java new file mode 100644 index 000000000..98af65820 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBEDataDecryptorFactory.java @@ -0,0 +1,26 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.S2K; +import org.spongycastle.openpgp.PGPException; + +public abstract class PBEDataDecryptorFactory + implements PGPDataDecryptorFactory +{ + private char[] passPhrase; + private PGPDigestCalculatorProvider calculatorProvider; + + protected PBEDataDecryptorFactory(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider) + { + this.passPhrase = passPhrase; + this.calculatorProvider = calculatorProvider; + } + + public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k) + throws PGPException + { + return PGPUtil.makeKeyFromPassPhrase(calculatorProvider, keyAlgorithm, s2k, passPhrase); + } + + public abstract byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] seckKeyData) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java new file mode 100644 index 000000000..a880fcf53 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBEKeyEncryptionMethodGenerator.java @@ -0,0 +1,91 @@ +package org.spongycastle.openpgp.operator; + +import java.security.SecureRandom; + +import org.spongycastle.bcpg.ContainedPacket; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SymmetricKeyEncSessionPacket; +import org.spongycastle.openpgp.PGPException; + +public abstract class PBEKeyEncryptionMethodGenerator + extends PGPKeyEncryptionMethodGenerator +{ + private char[] passPhrase; + private PGPDigestCalculator s2kDigestCalculator; + private S2K s2k; + private SecureRandom random; + private int s2kCount; + + protected PBEKeyEncryptionMethodGenerator( + char[] passPhrase, + PGPDigestCalculator s2kDigestCalculator) + { + this(passPhrase, s2kDigestCalculator, 0x60); + } + + protected PBEKeyEncryptionMethodGenerator( + char[] passPhrase, + PGPDigestCalculator s2kDigestCalculator, + int s2kCount) + { + this.passPhrase = passPhrase; + this.s2kDigestCalculator = s2kDigestCalculator; + + if (s2kCount < 0 || s2kCount > 0xff) + { + throw new IllegalArgumentException("s2kCount value outside of range 0 to 255."); + } + + this.s2kCount = s2kCount; + } + + public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public byte[] getKey(int encAlgorithm) + throws PGPException + { + if (s2k == null) + { + byte[] iv = new byte[8]; + + if (random == null) + { + random = new SecureRandom(); + } + + random.nextBytes(iv); + + s2k = new S2K(s2kDigestCalculator.getAlgorithm(), iv, s2kCount); + } + + return PGPUtil.makeKeyFromPassPhrase(s2kDigestCalculator, encAlgorithm, s2k, passPhrase); + } + + public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo) + throws PGPException + { + byte[] key = getKey(encAlgorithm); + + if (sessionInfo == null) + { + return new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, null); + } + + // + // the passed in session info has the an RSA/ElGamal checksum added to it, for PBE this is not included. + // + byte[] nSessionInfo = new byte[sessionInfo.length - 2]; + + System.arraycopy(sessionInfo, 0, nSessionInfo, 0, nSessionInfo.length); + + return new SymmetricKeyEncSessionPacket(encAlgorithm, s2k, encryptSessionInfo(encAlgorithm, key, nSessionInfo)); + } + + abstract protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java new file mode 100644 index 000000000..2f75702fa --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyDecryptor.java @@ -0,0 +1,31 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.S2K; +import org.spongycastle.openpgp.PGPException; + +public abstract class PBESecretKeyDecryptor +{ + private char[] passPhrase; + private PGPDigestCalculatorProvider calculatorProvider; + + protected PBESecretKeyDecryptor(char[] passPhrase, PGPDigestCalculatorProvider calculatorProvider) + { + this.passPhrase = passPhrase; + this.calculatorProvider = calculatorProvider; + } + + public PGPDigestCalculator getChecksumCalculator(int hashAlgorithm) + throws PGPException + { + return calculatorProvider.get(hashAlgorithm); + } + + public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k) + throws PGPException + { + return PGPUtil.makeKeyFromPassPhrase(calculatorProvider, keyAlgorithm, s2k, passPhrase); + } + + public abstract byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java new file mode 100644 index 000000000..2bd1bf8fe --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PBESecretKeyEncryptor.java @@ -0,0 +1,104 @@ +package org.spongycastle.openpgp.operator; + +import java.security.SecureRandom; + +import org.spongycastle.bcpg.S2K; +import org.spongycastle.openpgp.PGPException; + +public abstract class PBESecretKeyEncryptor +{ + protected int encAlgorithm; + protected char[] passPhrase; + protected PGPDigestCalculator s2kDigestCalculator; + protected int s2kCount; + protected S2K s2k; + + protected SecureRandom random; + + protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, SecureRandom random, char[] passPhrase) + { + this(encAlgorithm, s2kDigestCalculator, 0x60, random, passPhrase); + } + + protected PBESecretKeyEncryptor(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount, SecureRandom random, char[] passPhrase) + { + this.encAlgorithm = encAlgorithm; + this.passPhrase = passPhrase; + this.random = random; + this.s2kDigestCalculator = s2kDigestCalculator; + + if (s2kCount < 0 || s2kCount > 0xff) + { + throw new IllegalArgumentException("s2kCount value outside of range 0 to 255."); + } + + this.s2kCount = s2kCount; + } + + public int getAlgorithm() + { + return encAlgorithm; + } + + public int getHashAlgorithm() + { + if (s2kDigestCalculator != null) + { + return s2kDigestCalculator.getAlgorithm(); + } + + return -1; + } + + public byte[] getKey() + throws PGPException + { + return PGPUtil.makeKeyFromPassPhrase(s2kDigestCalculator, encAlgorithm, s2k, passPhrase); + } + + public S2K getS2K() + { + return s2k; + } + + /** + * Key encryption method invoked for V4 keys and greater. + * + * @param keyData raw key data + * @param keyOff offset into rawe key data + * @param keyLen length of key data to use. + * @return an encryption of the passed in keyData. + * @throws PGPException on error in the underlying encryption process. + */ + public byte[] encryptKeyData(byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + if (s2k == null) + { + byte[] iv = new byte[8]; + + random.nextBytes(iv); + + s2k = new S2K(s2kDigestCalculator.getAlgorithm(), iv, s2kCount); + } + + return encryptKeyData(getKey(), keyData, keyOff, keyLen); + } + + public abstract byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen) + throws PGPException; + + /** + * Encrypt the passed in keyData using the key and the iv provided. + * <p> + * This method is only used for processing version 3 keys. + * </p> + */ + public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + throw new PGPException("encryption of version 3 keys not supported."); + } + + public abstract byte[] getCipherIV(); +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java new file mode 100644 index 000000000..7a3891e62 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSigner.java @@ -0,0 +1,20 @@ +package org.spongycastle.openpgp.operator; + +import java.io.OutputStream; + +public interface PGPContentSigner +{ + public OutputStream getOutputStream(); + + byte[] getSignature(); + + byte[] getDigest(); + + int getType(); + + int getHashAlgorithm(); + + int getKeyAlgorithm(); + + long getKeyID(); +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java new file mode 100644 index 000000000..44f8c8dd6 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentSignerBuilder.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; + +public interface PGPContentSignerBuilder +{ + public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java new file mode 100644 index 000000000..7ed1aea63 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifier.java @@ -0,0 +1,20 @@ +package org.spongycastle.openpgp.operator; + +import java.io.OutputStream; + +public interface PGPContentVerifier +{ + public OutputStream getOutputStream(); + + int getHashAlgorithm(); + + int getKeyAlgorithm(); + + long getKeyID(); + + /** + * @param expected expected value of the signature on the data. + * @return true if the signature verifies, false otherwise + */ + boolean verify(byte[] expected); +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java new file mode 100644 index 000000000..9e8e4a944 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilder.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; + +public interface PGPContentVerifierBuilder +{ + public PGPContentVerifier build(final PGPPublicKey publicKey) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java new file mode 100644 index 000000000..44c138bf7 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPContentVerifierBuilderProvider.java @@ -0,0 +1,9 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; + +public interface PGPContentVerifierBuilderProvider +{ + public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java new file mode 100644 index 000000000..556e52556 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptor.java @@ -0,0 +1,12 @@ +package org.spongycastle.openpgp.operator; + +import java.io.InputStream; + +public interface PGPDataDecryptor +{ + InputStream getInputStream(InputStream in); + + int getBlockSize(); + + PGPDigestCalculator getIntegrityCalculator(); +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java new file mode 100644 index 000000000..0d0a41039 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorFactory.java @@ -0,0 +1,9 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; + +public interface PGPDataDecryptorFactory +{ + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java new file mode 100644 index 000000000..9e87a5651 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataDecryptorProvider.java @@ -0,0 +1,5 @@ +package org.spongycastle.openpgp.operator; + +public interface PGPDataDecryptorProvider +{ +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java new file mode 100644 index 000000000..93c1a0a52 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptor.java @@ -0,0 +1,12 @@ +package org.spongycastle.openpgp.operator; + +import java.io.OutputStream; + +public interface PGPDataEncryptor +{ + OutputStream getOutputStream(OutputStream out); + + PGPDigestCalculator getIntegrityCalculator(); + + int getBlockSize(); +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java new file mode 100644 index 000000000..c68c8d15b --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDataEncryptorBuilder.java @@ -0,0 +1,15 @@ +package org.spongycastle.openpgp.operator; + +import java.security.SecureRandom; + +import org.spongycastle.openpgp.PGPException; + +public interface PGPDataEncryptorBuilder +{ + int getAlgorithm(); + + PGPDataEncryptor build(byte[] keyBytes) + throws PGPException; + + SecureRandom getSecureRandom(); +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java new file mode 100644 index 000000000..32161e08a --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculator.java @@ -0,0 +1,35 @@ +package org.spongycastle.openpgp.operator; + +import java.io.OutputStream; + +public interface PGPDigestCalculator +{ + /** + * Return the algorithm number representing the digest implemented by + * this calculator. + * + * @return algorithm number + */ + int getAlgorithm(); + + /** + * Returns a stream that will accept data for the purpose of calculating + * a digest. Use org.spongycastle.util.io.TeeOutputStream if you want to accumulate + * the data on the fly as well. + * + * @return an OutputStream + */ + OutputStream getOutputStream(); + + /** + * Return the digest calculated on what has been written to the calculator's output stream. + * + * @return a digest. + */ + byte[] getDigest(); + + /** + * Reset the underlying digest calculator + */ + void reset(); +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java new file mode 100644 index 000000000..7b74ed960 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPDigestCalculatorProvider.java @@ -0,0 +1,9 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; + +public interface PGPDigestCalculatorProvider +{ + PGPDigestCalculator get(int algorithm) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java new file mode 100644 index 000000000..6aed6e580 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPKeyEncryptionMethodGenerator.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.bcpg.ContainedPacket; +import org.spongycastle.openpgp.PGPException; + +public abstract class PGPKeyEncryptionMethodGenerator +{ + public abstract ContainedPacket generate(int encAlgorithm, byte[] sessionInfo) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java new file mode 100644 index 000000000..a3e47b18a --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PGPUtil.java @@ -0,0 +1,216 @@ +package org.spongycastle.openpgp.operator; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.S2K; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.util.Strings; + +/** + * Basic utility class + */ +class PGPUtil + implements HashAlgorithmTags +{ + static byte[] makeKeyFromPassPhrase( + PGPDigestCalculator digestCalculator, + int algorithm, + S2K s2k, + char[] passPhrase) + throws PGPException + { + String algName = null; + int keySize = 0; + + switch (algorithm) + { + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + keySize = 192; + algName = "DES_EDE"; + break; + case SymmetricKeyAlgorithmTags.IDEA: + keySize = 128; + algName = "IDEA"; + break; + case SymmetricKeyAlgorithmTags.CAST5: + keySize = 128; + algName = "CAST5"; + break; + case SymmetricKeyAlgorithmTags.BLOWFISH: + keySize = 128; + algName = "Blowfish"; + break; + case SymmetricKeyAlgorithmTags.SAFER: + keySize = 128; + algName = "SAFER"; + break; + case SymmetricKeyAlgorithmTags.DES: + keySize = 64; + algName = "DES"; + break; + case SymmetricKeyAlgorithmTags.AES_128: + keySize = 128; + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.AES_192: + keySize = 192; + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.AES_256: + keySize = 256; + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.TWOFISH: + keySize = 256; + algName = "Twofish"; + break; + default: + throw new PGPException("unknown symmetric algorithm: " + algorithm); + } + + byte[] pBytes = Strings.toUTF8ByteArray(passPhrase); + byte[] keyBytes = new byte[(keySize + 7) / 8]; + + int generatedBytes = 0; + int loopCount = 0; + + if (s2k != null) + { + if (s2k.getHashAlgorithm() != digestCalculator.getAlgorithm()) + { + throw new PGPException("s2k/digestCalculator mismatch"); + } + } + else + { + if (digestCalculator.getAlgorithm() != HashAlgorithmTags.MD5) + { + throw new PGPException("digestCalculator not for MD5"); + } + } + + OutputStream dOut = digestCalculator.getOutputStream(); + + try + { + while (generatedBytes < keyBytes.length) + { + if (s2k != null) + { + for (int i = 0; i != loopCount; i++) + { + dOut.write(0); + } + + byte[] iv = s2k.getIV(); + + switch (s2k.getType()) + { + case S2K.SIMPLE: + dOut.write(pBytes); + break; + case S2K.SALTED: + dOut.write(iv); + dOut.write(pBytes); + break; + case S2K.SALTED_AND_ITERATED: + long count = s2k.getIterationCount(); + dOut.write(iv); + dOut.write(pBytes); + + count -= iv.length + pBytes.length; + + while (count > 0) + { + if (count < iv.length) + { + dOut.write(iv, 0, (int)count); + break; + } + else + { + dOut.write(iv); + count -= iv.length; + } + + if (count < pBytes.length) + { + dOut.write(pBytes, 0, (int)count); + count = 0; + } + else + { + dOut.write(pBytes); + count -= pBytes.length; + } + } + break; + default: + throw new PGPException("unknown S2K type: " + s2k.getType()); + } + } + else + { + for (int i = 0; i != loopCount; i++) + { + dOut.write((byte)0); + } + + dOut.write(pBytes); + } + + dOut.close(); + + byte[] dig = digestCalculator.getDigest(); + + if (dig.length > (keyBytes.length - generatedBytes)) + { + System.arraycopy(dig, 0, keyBytes, generatedBytes, keyBytes.length - generatedBytes); + } + else + { + System.arraycopy(dig, 0, keyBytes, generatedBytes, dig.length); + } + + generatedBytes += dig.length; + + loopCount++; + } + } + catch (IOException e) + { + throw new PGPException("exception calculating digest: " + e.getMessage(), e); + } + + for (int i = 0; i != pBytes.length; i++) + { + pBytes[i] = 0; + } + + return keyBytes; + } + + public static byte[] makeKeyFromPassPhrase( + PGPDigestCalculatorProvider digCalcProvider, + int algorithm, + S2K s2k, + char[] passPhrase) + throws PGPException + { + PGPDigestCalculator digestCalculator; + + if (s2k != null) + { + digestCalculator = digCalcProvider.get(s2k.getHashAlgorithm()); + } + else + { + digestCalculator = digCalcProvider.get(HashAlgorithmTags.MD5); + } + + return makeKeyFromPassPhrase(digestCalculator, algorithm, s2k, passPhrase); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java new file mode 100644 index 000000000..35d2a01d4 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyDataDecryptorFactory.java @@ -0,0 +1,10 @@ +package org.spongycastle.openpgp.operator; + +import org.spongycastle.openpgp.PGPException; + +public interface PublicKeyDataDecryptorFactory + extends PGPDataDecryptorFactory +{ + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java new file mode 100644 index 000000000..8030b946e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/PublicKeyKeyEncryptionMethodGenerator.java @@ -0,0 +1,100 @@ +package org.spongycastle.openpgp.operator; + +import java.io.IOException; +import java.math.BigInteger; + +import org.spongycastle.bcpg.ContainedPacket; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyEncSessionPacket; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; + +public abstract class PublicKeyKeyEncryptionMethodGenerator + extends PGPKeyEncryptionMethodGenerator +{ + private PGPPublicKey pubKey; + + protected PublicKeyKeyEncryptionMethodGenerator( + PGPPublicKey pubKey) + { + this.pubKey = pubKey; + + switch (pubKey.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + break; + case PGPPublicKey.ECDH: + break; + case PGPPublicKey.DSA: + throw new IllegalArgumentException("Can't use DSA for encryption."); + case PGPPublicKey.ECDSA: + throw new IllegalArgumentException("Can't use ECDSA for encryption."); + default: + throw new IllegalArgumentException("unknown asymmetric algorithm: " + pubKey.getAlgorithm()); + } + } + + public byte[][] processSessionInfo( + byte[] encryptedSessionInfo) + throws PGPException + { + byte[][] data; + + switch (pubKey.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + data = new byte[1][]; + + data[0] = convertToEncodedMPI(encryptedSessionInfo); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + byte[] b1 = new byte[encryptedSessionInfo.length / 2]; + byte[] b2 = new byte[encryptedSessionInfo.length / 2]; + + System.arraycopy(encryptedSessionInfo, 0, b1, 0, b1.length); + System.arraycopy(encryptedSessionInfo, b1.length, b2, 0, b2.length); + + data = new byte[2][]; + data[0] = convertToEncodedMPI(b1); + data[1] = convertToEncodedMPI(b2); + break; + case PGPPublicKey.ECDH: + data = new byte[1][]; + + data[0] = encryptedSessionInfo; + break; + default: + throw new PGPException("unknown asymmetric algorithm: " + pubKey.getAlgorithm()); + } + + return data; + } + + private byte[] convertToEncodedMPI(byte[] encryptedSessionInfo) + throws PGPException + { + try + { + return new MPInteger(new BigInteger(1, encryptedSessionInfo)).getEncoded(); + } + catch (IOException e) + { + throw new PGPException("Invalid MPI encoding: " + e.getMessage(), e); + } + } + + public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo) + throws PGPException + { + return new PublicKeyEncSessionPacket(pubKey.getKeyID(), pubKey.getAlgorithm(), processSessionInfo(encryptSessionInfo(pubKey, sessionInfo))); + } + + abstract protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo) + throws PGPException; +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java new file mode 100644 index 000000000..8d76d24e0 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/RFC6637KDFCalculator.java @@ -0,0 +1,116 @@ +package org.spongycastle.openpgp.operator; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.util.encoders.Hex; + +/** + * Calculator for the EC based KDF algorithm described in RFC 6637 + */ +public class RFC6637KDFCalculator +{ + // "Anonymous Sender ", which is the octet sequence + private static final byte[] ANONYMOUS_SENDER = Hex.decode("416E6F6E796D6F75732053656E64657220202020"); + + private final PGPDigestCalculator digCalc; + private final int keyAlgorithm; + + public RFC6637KDFCalculator(PGPDigestCalculator digCalc, int keyAlgorithm) + { + this.digCalc = digCalc; + this.keyAlgorithm = keyAlgorithm; + } + + public byte[] createKey(ASN1ObjectIdentifier curveOID, ECPoint s, byte[] recipientFingerPrint) + throws PGPException + { + try + { + // RFC 6637 - Section 8 + // curve_OID_len = (byte)len(curve_OID); + // Param = curve_OID_len || curve_OID || public_key_alg_ID || 03 + // || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap || "Anonymous + // Sender " || recipient_fingerprint; + // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap + // Compute Z = KDF( S, Z_len, Param ); + ByteArrayOutputStream pOut = new ByteArrayOutputStream(); + + byte[] encOid = curveOID.getEncoded(); + + pOut.write(encOid, 1, encOid.length - 1); + pOut.write(PublicKeyAlgorithmTags.ECDH); + pOut.write(0x03); + pOut.write(0x01); + pOut.write(digCalc.getAlgorithm()); + pOut.write(keyAlgorithm); + pOut.write(ANONYMOUS_SENDER); + pOut.write(recipientFingerPrint); + + return KDF(digCalc, s, getKeyLen(keyAlgorithm), pOut.toByteArray()); + } + catch (IOException e) + { + throw new PGPException("Exception performing KDF: " + e.getMessage(), e); + } + } + + // RFC 6637 - Section 7 + // Implements KDF( X, oBits, Param ); + // Input: point X = (x,y) + // oBits - the desired size of output + // hBits - the size of output of hash function Hash + // Param - octets representing the parameters + // Assumes that oBits <= hBits + // Convert the point X to the octet string, see section 6: + // ZB' = 04 || x || y + // and extract the x portion from ZB' + // ZB = x; + // MB = Hash ( 00 || 00 || 00 || 01 || ZB || Param ); + // return oBits leftmost bits of MB. + private static byte[] KDF(PGPDigestCalculator digCalc, ECPoint s, int keyLen, byte[] param) + throws IOException + { + byte[] ZB = s.getXCoord().getEncoded(); + + OutputStream dOut = digCalc.getOutputStream(); + + dOut.write(0x00); + dOut.write(0x00); + dOut.write(0x00); + dOut.write(0x01); + dOut.write(0x01); + dOut.write(ZB); + dOut.write(param); + + byte[] digest = digCalc.getDigest(); + + byte[] key = new byte[keyLen]; + + System.arraycopy(digest, 0, key, 0, key.length); + + return key; + } + + private static int getKeyLen(int algID) + throws PGPException + { + switch (algID) + { + case SymmetricKeyAlgorithmTags.AES_128: + return 16; + case SymmetricKeyAlgorithmTags.AES_192: + return 24; + case SymmetricKeyAlgorithmTags.AES_256: + return 32; + default: + throw new PGPException("unknown symmetric algorithm ID: " + algID); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java new file mode 100644 index 000000000..5e3102f37 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcImplProvider.java @@ -0,0 +1,138 @@ +package org.spongycastle.openpgp.operator.bc; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.digests.MD2Digest; +import org.spongycastle.crypto.digests.MD5Digest; +import org.spongycastle.crypto.digests.RIPEMD160Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.digests.SHA224Digest; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.digests.SHA384Digest; +import org.spongycastle.crypto.digests.SHA512Digest; +import org.spongycastle.crypto.digests.TigerDigest; +import org.spongycastle.crypto.encodings.PKCS1Encoding; +import org.spongycastle.crypto.engines.AESEngine; +import org.spongycastle.crypto.engines.BlowfishEngine; +import org.spongycastle.crypto.engines.CAST5Engine; +import org.spongycastle.crypto.engines.DESEngine; +import org.spongycastle.crypto.engines.DESedeEngine; +import org.spongycastle.crypto.engines.ElGamalEngine; +import org.spongycastle.crypto.engines.RSABlindedEngine; +import org.spongycastle.crypto.engines.TwofishEngine; +import org.spongycastle.crypto.signers.DSADigestSigner; +import org.spongycastle.crypto.signers.DSASigner; +import org.spongycastle.crypto.signers.RSADigestSigner; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; + +class BcImplProvider +{ + static Digest createDigest(int algorithm) + throws PGPException + { + switch (algorithm) + { + case HashAlgorithmTags.SHA1: + return new SHA1Digest(); + case HashAlgorithmTags.SHA224: + return new SHA224Digest(); + case HashAlgorithmTags.SHA256: + return new SHA256Digest(); + case HashAlgorithmTags.SHA384: + return new SHA384Digest(); + case HashAlgorithmTags.SHA512: + return new SHA512Digest(); + case HashAlgorithmTags.MD2: + return new MD2Digest(); + case HashAlgorithmTags.MD5: + return new MD5Digest(); + case HashAlgorithmTags.RIPEMD160: + return new RIPEMD160Digest(); + case HashAlgorithmTags.TIGER_192: + return new TigerDigest(); + default: + throw new PGPException("cannot recognise digest"); + } + } + + static Signer createSigner(int keyAlgorithm, int hashAlgorithm) + throws PGPException + { + switch(keyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + return new RSADigestSigner(createDigest(hashAlgorithm)); + case PublicKeyAlgorithmTags.DSA: + return new DSADigestSigner(new DSASigner(), createDigest(hashAlgorithm)); + default: + throw new PGPException("cannot recognise keyAlgorithm"); + } + } + + static BlockCipher createBlockCipher(int encAlgorithm) + throws PGPException + { + BlockCipher engine; + + switch (encAlgorithm) + { + case SymmetricKeyAlgorithmTags.AES_128: + case SymmetricKeyAlgorithmTags.AES_192: + case SymmetricKeyAlgorithmTags.AES_256: + engine = new AESEngine(); + break; + case SymmetricKeyAlgorithmTags.BLOWFISH: + engine = new BlowfishEngine(); + break; + case SymmetricKeyAlgorithmTags.CAST5: + engine = new CAST5Engine(); + break; + case SymmetricKeyAlgorithmTags.DES: + engine = new DESEngine(); + break; + case SymmetricKeyAlgorithmTags.TWOFISH: + engine = new TwofishEngine(); + break; + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + engine = new DESedeEngine(); + break; + default: + throw new PGPException("cannot recognise cipher"); + } + + return engine; + } + + static AsymmetricBlockCipher createPublicKeyCipher(int encAlgorithm) + throws PGPException + { + AsymmetricBlockCipher c; + + switch (encAlgorithm) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + c = new PKCS1Encoding(new RSABlindedEngine()); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + c = new PKCS1Encoding(new ElGamalEngine()); + break; + case PGPPublicKey.DSA: + throw new PGPException("Can't use DSA for encryption."); + case PGPPublicKey.ECDSA: + throw new PGPException("Can't use ECDSA for encryption."); + default: + throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm); + } + + return c; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java new file mode 100644 index 000000000..9b9b7f26e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcKeyFingerprintCalculator.java @@ -0,0 +1,68 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; + +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.MD5Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; + +public class BcKeyFingerprintCalculator + implements KeyFingerPrintCalculator +{ + public byte[] calculateFingerprint(PublicKeyPacket publicPk) + throws PGPException + { + BCPGKey key = publicPk.getKey(); + Digest digest; + + if (publicPk.getVersion() <= 3) + { + RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key; + + try + { + digest = new MD5Digest(); + + byte[] bytes = new MPInteger(rK.getModulus()).getEncoded(); + digest.update(bytes, 2, bytes.length - 2); + + bytes = new MPInteger(rK.getPublicExponent()).getEncoded(); + digest.update(bytes, 2, bytes.length - 2); + } + catch (IOException e) + { + throw new PGPException("can't encode key components: " + e.getMessage(), e); + } + } + else + { + try + { + byte[] kBytes = publicPk.getEncodedContents(); + + digest = new SHA1Digest(); + + digest.update((byte)0x99); + digest.update((byte)(kBytes.length >> 8)); + digest.update((byte)kBytes.length); + digest.update(kBytes, 0, kBytes.length); + } + catch (IOException e) + { + throw new PGPException("can't encode key components: " + e.getMessage(), e); + } + } + + byte[] digBuf = new byte[digest.getDigestSize()]; + + digest.doFinal(digBuf, 0); + + return digBuf; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java new file mode 100644 index 000000000..e45fee74c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java @@ -0,0 +1,67 @@ +package org.spongycastle.openpgp.operator.bc; + +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; + +/** + * A decryptor factory for handling PBE decryption operations. + */ +public class BcPBEDataDecryptorFactory + extends PBEDataDecryptorFactory +{ + /** + * Base constructor. + * + * @param pass the passphrase to use as the primary source of key material. + * @param calculatorProvider a digest calculator provider to provide calculators to support the key generation calculation required. + */ + public BcPBEDataDecryptorFactory(char[] pass, BcPGPDigestCalculatorProvider calculatorProvider) + { + super(pass, calculatorProvider); + } + + public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData) + throws PGPException + { + try + { + if (secKeyData != null && secKeyData.length > 0) + { + BlockCipher engine = BcImplProvider.createBlockCipher(keyAlgorithm); + BufferedBlockCipher cipher = BcUtil.createSymmetricKeyWrapper(false, engine, key, new byte[engine.getBlockSize()]); + + byte[] out = new byte[secKeyData.length]; + + int len = cipher.processBytes(secKeyData, 0, secKeyData.length, out, 0); + + len += cipher.doFinal(out, len); + + return out; + } + else + { + byte[] keyBytes = new byte[key.length + 1]; + + keyBytes[0] = (byte)keyAlgorithm; + System.arraycopy(key, 0, keyBytes, 1, key.length); + + return keyBytes; + } + } + catch (Exception e) + { + throw new PGPException("Exception recovering session info", e); + } + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm); + + return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java new file mode 100644 index 000000000..8899de920 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBEKeyEncryptionMethodGenerator.java @@ -0,0 +1,97 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +/** + * A BC lightweight method generator for supporting PBE based encryption operations. + */ +public class BcPBEKeyEncryptionMethodGenerator + extends PBEKeyEncryptionMethodGenerator +{ + /** + * Create a PBE encryption method generator using the provided calculator for key calculation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kDigestCalculator the digest calculator to use for key calculation. + */ + public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator) + { + super(passPhrase, s2kDigestCalculator); + } + + /** + * Create a PBE encryption method generator using the default SHA-1 digest calculator for key calculation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + */ + public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase) + { + this(passPhrase, new SHA1PGPDigestCalculator()); + } + + /** + * Create a PBE encryption method generator using the provided calculator and S2K count for key calculation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kDigestCalculator the digest calculator to use for key calculation. + * @param s2kCount the S2K count to use. + */ + public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator, int s2kCount) + { + super(passPhrase, s2kDigestCalculator, s2kCount); + } + + /** + * Create a PBE encryption method generator using the default SHA-1 digest calculator and + * a S2K count other than the default of 0x60 for key calculation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kCount the S2K count to use. + */ + public BcPBEKeyEncryptionMethodGenerator(char[] passPhrase, int s2kCount) + { + super(passPhrase, new SHA1PGPDigestCalculator(), s2kCount); + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current generator. + */ + public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + super.setSecureRandom(random); + + return this; + } + + protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo) + throws PGPException + { + try + { + BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm); + BufferedBlockCipher cipher = BcUtil.createSymmetricKeyWrapper(true, engine, key, new byte[engine.getBlockSize()]); + + byte[] out = new byte[sessionInfo.length]; + + int len = cipher.processBytes(sessionInfo, 0, sessionInfo.length, out, 0); + + len += cipher.doFinal(out, len); + + return out; + } + catch (InvalidCipherTextException e) + { + throw new PGPException("encryption failed: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java new file mode 100644 index 000000000..bf0a0db9a --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyDecryptorBuilder.java @@ -0,0 +1,43 @@ +package org.spongycastle.openpgp.operator.bc; + +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class BcPBESecretKeyDecryptorBuilder +{ + private PGPDigestCalculatorProvider calculatorProvider; + + public BcPBESecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider) + { + this.calculatorProvider = calculatorProvider; + } + + public PBESecretKeyDecryptor build(char[] passPhrase) + { + return new PBESecretKeyDecryptor(passPhrase, calculatorProvider) + { + public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + BufferedBlockCipher c = BcUtil.createSymmetricKeyWrapper(false, BcImplProvider.createBlockCipher(encAlgorithm), key, iv); + + byte[] out = new byte[keyLen]; + int outLen = c.processBytes(keyData, keyOff, keyLen, out, 0); + + outLen += c.doFinal(out, outLen); + + return out; + } + catch (InvalidCipherTextException e) + { + throw new PGPException("decryption failed: " + e.getMessage(), e); + } + } + }; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java new file mode 100644 index 000000000..aea664ae0 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPBESecretKeyEncryptorBuilder.java @@ -0,0 +1,142 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +public class BcPBESecretKeyEncryptorBuilder +{ + private int encAlgorithm; + private PGPDigestCalculator s2kDigestCalculator; + private SecureRandom random; + private int s2kCount = 0x60; + + public BcPBESecretKeyEncryptorBuilder(int encAlgorithm) + { + this(encAlgorithm, new SHA1PGPDigestCalculator()); + } + + /** + * Create an SecretKeyEncryptorBuilder with the S2K count different to the default of 0x60. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kCount iteration count to use for S2K function. + */ + public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, int s2kCount) + { + this(encAlgorithm, new SHA1PGPDigestCalculator(), s2kCount); + } + + /** + * Create a builder which will make encryptors using the passed in digest calculator. If a MD5 calculator is + * passed in the builder will assume the encryptors are for use with version 3 keys. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kDigestCalculator digest calculator to use. + */ + public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator) + { + this(encAlgorithm, s2kDigestCalculator, 0x60); + } + + /** + * Create an SecretKeyEncryptorBuilder with the S2k count different to the default of 0x60, and the S2K digest + * different from SHA-1. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kDigestCalculator digest calculator to use. + * @param s2kCount iteration count to use for S2K function. + */ + public BcPBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount) + { + this.encAlgorithm = encAlgorithm; + this.s2kDigestCalculator = s2kDigestCalculator; + + if (s2kCount < 0 || s2kCount > 0xff) + { + throw new IllegalArgumentException("s2KCount value outside of range 0 to 255."); + } + + this.s2kCount = s2kCount; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current builder. + */ + public BcPBESecretKeyEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PBESecretKeyEncryptor build(char[] passPhrase) + { + if (this.random == null) + { + this.random = new SecureRandom(); + } + + return new PBESecretKeyEncryptor(encAlgorithm, s2kDigestCalculator, s2kCount, this.random, passPhrase) + { + private byte[] iv; + + public byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + return encryptKeyData(key, null, keyData, keyOff, keyLen); + } + + public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + BlockCipher engine = BcImplProvider.createBlockCipher(this.encAlgorithm); + + if (iv != null) + { // to deal with V3 key encryption + this.iv = iv; + } + else + { + if (this.random == null) + { + this.random = new SecureRandom(); + } + + this.iv = iv = new byte[engine.getBlockSize()]; + + this.random.nextBytes(iv); + } + + BufferedBlockCipher c = BcUtil.createSymmetricKeyWrapper(true, engine, key, iv); + + byte[] out = new byte[keyLen]; + int outLen = c.processBytes(keyData, keyOff, keyLen, out, 0); + + outLen += c.doFinal(out, outLen); + + return out; + } + catch (InvalidCipherTextException e) + { + throw new PGPException("decryption failed: " + e.getMessage(), e); + } + } + + public byte[] getCipherIV() + { + return iv; + } + }; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java new file mode 100644 index 000000000..cd98ef38c --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentSignerBuilder.java @@ -0,0 +1,98 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.OutputStream; +import java.security.SecureRandom; + +import org.spongycastle.crypto.CryptoException; +import org.spongycastle.crypto.Signer; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.operator.PGPContentSigner; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.util.io.TeeOutputStream; + +public class BcPGPContentSignerBuilder + implements PGPContentSignerBuilder +{ + private BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider(); + private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); + private int hashAlgorithm; + private SecureRandom random; + private int keyAlgorithm; + + public BcPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public BcPGPContentSignerBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PGPContentSigner build(final int signatureType, final PGPPrivateKey privateKey) + throws PGPException + { + final PGPDigestCalculator digestCalculator = digestCalculatorProvider.get(hashAlgorithm); + final Signer signer = BcImplProvider.createSigner(keyAlgorithm, hashAlgorithm); + + if (random != null) + { + signer.init(true, new ParametersWithRandom(keyConverter.getPrivateKey(privateKey), random)); + } + else + { + signer.init(true, keyConverter.getPrivateKey(privateKey)); + } + + return new PGPContentSigner() + { + public int getType() + { + return signatureType; + } + + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + public long getKeyID() + { + return privateKey.getKeyID(); + } + + public OutputStream getOutputStream() + { + return new TeeOutputStream(new SignerOutputStream(signer), digestCalculator.getOutputStream()); + } + + public byte[] getSignature() + { + try + { + return signer.generateSignature(); + } + catch (CryptoException e) + { // TODO: need a specific runtime exception for PGP operators. + throw new IllegalStateException("unable to create signature"); + } + } + + public byte[] getDigest() + { + return digestCalculator.getDigest(); + } + }; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java new file mode 100644 index 000000000..a2cfbf911 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPContentVerifierBuilderProvider.java @@ -0,0 +1,75 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.OutputStream; + +import org.spongycastle.crypto.Signer; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPContentVerifier; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider; + +public class BcPGPContentVerifierBuilderProvider + implements PGPContentVerifierBuilderProvider +{ + private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); + + public BcPGPContentVerifierBuilderProvider() + { + } + + public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm) + throws PGPException + { + return new BcPGPContentVerifierBuilder(keyAlgorithm, hashAlgorithm); + } + + private class BcPGPContentVerifierBuilder + implements PGPContentVerifierBuilder + { + private int hashAlgorithm; + private int keyAlgorithm; + + public BcPGPContentVerifierBuilder(int keyAlgorithm, int hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public PGPContentVerifier build(final PGPPublicKey publicKey) + throws PGPException + { + final Signer signer = BcImplProvider.createSigner(keyAlgorithm, hashAlgorithm); + + signer.init(false, keyConverter.getPublicKey(publicKey)); + + return new PGPContentVerifier() + { + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + public long getKeyID() + { + return publicKey.getKeyID(); + } + + public boolean verify(byte[] expected) + { + return signer.verifySignature(expected); + } + + public OutputStream getOutputStream() + { + return new SignerOutputStream(signer); + } + }; + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java new file mode 100644 index 000000000..5c65ce499 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDataEncryptorBuilder.java @@ -0,0 +1,118 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.OutputStream; +import java.security.SecureRandom; + +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.io.CipherOutputStream; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PGPDataEncryptor; +import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +public class BcPGPDataEncryptorBuilder + implements PGPDataEncryptorBuilder +{ + private SecureRandom random; + private boolean withIntegrityPacket; + private int encAlgorithm; + + public BcPGPDataEncryptorBuilder(int encAlgorithm) + { + this.encAlgorithm = encAlgorithm; + + if (encAlgorithm == 0) + { + throw new IllegalArgumentException("null cipher specified"); + } + } + + /** + * Determine whether or not the resulting encrypted data will be protected using an integrity packet. + * + * @param withIntegrityPacket true if an integrity packet is to be included, false otherwise. + * @return the current builder. + */ + public BcPGPDataEncryptorBuilder setWithIntegrityPacket(boolean withIntegrityPacket) + { + this.withIntegrityPacket = withIntegrityPacket; + + return this; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current builder. + */ + public BcPGPDataEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public int getAlgorithm() + { + return encAlgorithm; + } + + public SecureRandom getSecureRandom() + { + if (random == null) + { + random = new SecureRandom(); + } + + return random; + } + + public PGPDataEncryptor build(byte[] keyBytes) + throws PGPException + { + return new MyPGPDataEncryptor(keyBytes); + } + + private class MyPGPDataEncryptor + implements PGPDataEncryptor + { + private final BufferedBlockCipher c; + + MyPGPDataEncryptor(byte[] keyBytes) + throws PGPException + { + BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm); + + try + { + c = BcUtil.createStreamCipher(true, engine, withIntegrityPacket, keyBytes); + } + catch (IllegalArgumentException e) + { + throw new PGPException("invalid parameters: " + e.getMessage(), e); + } + } + + public OutputStream getOutputStream(OutputStream out) + { + return new CipherOutputStream(out, c); + } + + public PGPDigestCalculator getIntegrityCalculator() + { + if (withIntegrityPacket) + { + return new SHA1PGPDigestCalculator(); + } + + return null; + } + + public int getBlockSize() + { + return c.getBlockSize(); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java new file mode 100644 index 000000000..50d5fc736 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPDigestCalculatorProvider.java @@ -0,0 +1,82 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class BcPGPDigestCalculatorProvider + implements PGPDigestCalculatorProvider +{ + public PGPDigestCalculator get(final int algorithm) + throws PGPException + { + final Digest dig = BcImplProvider.createDigest(algorithm); + + final DigestOutputStream stream = new DigestOutputStream(dig); + + return new PGPDigestCalculator() + { + public int getAlgorithm() + { + return algorithm; + } + + public OutputStream getOutputStream() + { + return stream; + } + + public byte[] getDigest() + { + return stream.getDigest(); + } + + public void reset() + { + dig.reset(); + } + }; + } + + private class DigestOutputStream + extends OutputStream + { + private Digest dig; + + DigestOutputStream(Digest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes, 0, bytes.length); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + + byte[] getDigest() + { + byte[] d = new byte[dig.getDigestSize()]; + + dig.doFinal(d, 0); + + return d; + } + } +}
\ No newline at end of file diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java new file mode 100644 index 000000000..309bc8a82 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyConverter.java @@ -0,0 +1,199 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.util.Date; + +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.DSAPublicBCPGKey; +import org.spongycastle.bcpg.DSASecretBCPGKey; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.ECDSAPublicBCPGKey; +import org.spongycastle.bcpg.ElGamalPublicBCPGKey; +import org.spongycastle.bcpg.ElGamalSecretBCPGKey; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.bcpg.RSASecretBCPGKey; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.DSAParameters; +import org.spongycastle.crypto.params.DSAPrivateKeyParameters; +import org.spongycastle.crypto.params.DSAPublicKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.params.ElGamalParameters; +import org.spongycastle.crypto.params.ElGamalPrivateKeyParameters; +import org.spongycastle.crypto.params.ElGamalPublicKeyParameters; +import org.spongycastle.crypto.params.RSAKeyParameters; +import org.spongycastle.crypto.params.RSAPrivateCrtKeyParameters; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; + +public class BcPGPKeyConverter +{ + /** + * Create a PGPPublicKey from the passed in JCA one. + * <p/> + * Note: the time passed in affects the value of the key's keyID, so you probably only want + * to do this once for a JCA key, or make sure you keep track of the time you used. + * + * @param algorithm asymmetric algorithm type representing the public key. + * @param pubKey actual public key to associate. + * @param time date of creation. + * @throws PGPException on key creation problem. + */ + public PGPPublicKey getPGPPublicKey(int algorithm, AsymmetricKeyParameter pubKey, Date time) + throws PGPException + { + BCPGKey bcpgKey; + + if (pubKey instanceof RSAKeyParameters) + { + RSAKeyParameters rK = (RSAKeyParameters)pubKey; + + bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getExponent()); + } + else if (pubKey instanceof DSAPublicKeyParameters) + { + DSAPublicKeyParameters dK = (DSAPublicKeyParameters)pubKey; + DSAParameters dP = dK.getParameters(); + + bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY()); + } + else if (pubKey instanceof ElGamalPublicKeyParameters) + { + ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters)pubKey; + ElGamalParameters eS = eK.getParameters(); + + bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY()); + } + else if (pubKey instanceof ECPublicKeyParameters) + { + ECPublicKeyParameters eK = (ECPublicKeyParameters)pubKey; + + if (algorithm == PGPPublicKey.EC) + { // TODO: KDF parameters + bcpgKey = new ECDHPublicBCPGKey(null, eK.getQ(), 0, 0); + } + else + { + bcpgKey = new ECDSAPublicBCPGKey(null, eK.getQ()); + } + } + else + { + throw new PGPException("unknown key class"); + } + + return new PGPPublicKey(new PublicKeyPacket(algorithm, time, bcpgKey), new BcKeyFingerprintCalculator()); + } + + public PGPPrivateKey getPGPPrivateKey(PGPPublicKey pubKey, AsymmetricKeyParameter privKey) + throws PGPException + { + BCPGKey privPk; + + switch (pubKey.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_SIGN: + case PGPPublicKey.RSA_GENERAL: + RSAPrivateCrtKeyParameters rsK = (RSAPrivateCrtKeyParameters)privKey; + + privPk = new RSASecretBCPGKey(rsK.getExponent(), rsK.getP(), rsK.getQ()); + break; + case PGPPublicKey.DSA: + DSAPrivateKeyParameters dsK = (DSAPrivateKeyParameters)privKey; + + privPk = new DSASecretBCPGKey(dsK.getX()); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters)privKey; + + privPk = new ElGamalSecretBCPGKey(esK.getX()); + break; + default: + throw new PGPException("unknown key class"); + } + return new PGPPrivateKey(pubKey.getKeyID(), pubKey.getPublicKeyPacket(), privPk); + } + + public AsymmetricKeyParameter getPublicKey(PGPPublicKey publicKey) + throws PGPException + { + PublicKeyPacket publicPk = publicKey.getPublicKeyPacket(); + + try + { + switch (publicPk.getAlgorithm()) + { + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + RSAPublicBCPGKey rsaK = (RSAPublicBCPGKey)publicPk.getKey(); + + return new RSAKeyParameters(false, rsaK.getModulus(), rsaK.getPublicExponent()); + case PublicKeyAlgorithmTags.DSA: + DSAPublicBCPGKey dsaK = (DSAPublicBCPGKey)publicPk.getKey(); + + return new DSAPublicKeyParameters(dsaK.getY(), new DSAParameters(dsaK.getP(), dsaK.getQ(), dsaK.getG())); + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + ElGamalPublicBCPGKey elK = (ElGamalPublicBCPGKey)publicPk.getKey(); + + return new ElGamalPublicKeyParameters(elK.getY(), new ElGamalParameters(elK.getP(), elK.getG())); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("exception constructing public key", e); + } + } + + public AsymmetricKeyParameter getPrivateKey(PGPPrivateKey privKey) + throws PGPException + { + PublicKeyPacket pubPk = privKey.getPublicKeyPacket(); + BCPGKey privPk = privKey.getPrivateKeyDataPacket(); + + try + { + switch (pubPk.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + case PGPPublicKey.RSA_SIGN: + RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey(); + RSASecretBCPGKey rsaPriv = (RSASecretBCPGKey)privPk; + + return new RSAPrivateCrtKeyParameters(rsaPriv.getModulus(), rsaPub.getPublicExponent(), rsaPriv.getPrivateExponent(), rsaPriv.getPrimeP(), rsaPriv.getPrimeQ(), rsaPriv.getPrimeExponentP(), rsaPriv.getPrimeExponentQ(), rsaPriv.getCrtCoefficient()); + case PGPPublicKey.DSA: + DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey(); + DSASecretBCPGKey dsaPriv = (DSASecretBCPGKey)privPk; + + return new DSAPrivateKeyParameters(dsaPriv.getX(), new DSAParameters(dsaPub.getP(), dsaPub.getQ(), dsaPub.getG())); + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey(); + ElGamalSecretBCPGKey elPriv = (ElGamalSecretBCPGKey)privPk; + + return new ElGamalPrivateKeyParameters(elPriv.getX(), new ElGamalParameters(elPub.getP(), elPub.getG())); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception constructing key", e); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java new file mode 100644 index 000000000..6e182077e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPGPKeyPair.java @@ -0,0 +1,33 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.util.Date; + +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyPair; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; + +public class BcPGPKeyPair + extends PGPKeyPair +{ + private static PGPPublicKey getPublicKey(int algorithm, AsymmetricKeyParameter pubKey, Date date) + throws PGPException + { + return new BcPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, date); + } + + private static PGPPrivateKey getPrivateKey(PGPPublicKey pub, AsymmetricKeyParameter privKey) + throws PGPException + { + return new BcPGPKeyConverter().getPGPPrivateKey(pub, privKey); + } + + public BcPGPKeyPair(int algorithm, AsymmetricCipherKeyPair keyPair, Date date) + throws PGPException + { + this.pub = getPublicKey(algorithm, (AsymmetricKeyParameter)keyPair.getPublic(), date); + this.priv = getPrivateKey(this.pub, (AsymmetricKeyParameter)keyPair.getPrivate()); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java new file mode 100644 index 000000000..f4844cabb --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java @@ -0,0 +1,100 @@ +package org.spongycastle.openpgp.operator.bc; + +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedAsymmetricBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ElGamalPrivateKeyParameters; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; + +/** + * A decryptor factory for handling public key decryption operations. + */ +public class BcPublicKeyDataDecryptorFactory + implements PublicKeyDataDecryptorFactory +{ + private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); + private PGPPrivateKey privKey; + + public BcPublicKeyDataDecryptorFactory(PGPPrivateKey privKey) + { + this.privKey = privKey; + } + + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) + throws PGPException + { + try + { + AsymmetricBlockCipher c = BcImplProvider.createPublicKeyCipher(keyAlgorithm); + + AsymmetricKeyParameter key = keyConverter.getPrivateKey(privKey); + + BufferedAsymmetricBlockCipher c1 = new BufferedAsymmetricBlockCipher(c); + + c1.init(false, key); + + if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT + || keyAlgorithm == PGPPublicKey.RSA_GENERAL) + { + byte[] bi = secKeyData[0]; + + c1.processBytes(bi, 2, bi.length - 2); + } + else + { + BcPGPKeyConverter converter = new BcPGPKeyConverter(); + ElGamalPrivateKeyParameters parms = (ElGamalPrivateKeyParameters) converter.getPrivateKey(privKey); + int size = (parms.getParameters().getP().bitLength() + 7) / 8; + byte[] tmp = new byte[size]; + + byte[] bi = secKeyData[0]; // encoded MPI + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... + { + c1.processBytes(bi, 3, bi.length - 3); + } + else + { + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.processBytes(tmp, 0, tmp.length); + } + + bi = secKeyData[1]; // encoded MPI + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = 0; + } + + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... + { + c1.processBytes(bi, 3, bi.length - 3); + } + else + { + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.processBytes(tmp, 0, tmp.length); + } + } + + return c1.doFinal(); + } + catch (InvalidCipherTextException e) + { + throw new PGPException("exception encrypting session info: " + e.getMessage(), e); + } + + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + BlockCipher engine = BcImplProvider.createBlockCipher(encAlgorithm); + + return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java new file mode 100644 index 000000000..224c3733f --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcPublicKeyKeyEncryptionMethodGenerator.java @@ -0,0 +1,68 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.security.SecureRandom; + +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.InvalidCipherTextException; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; + +/** + * A method generator for supporting public key based encryption operations. + */ +public class BcPublicKeyKeyEncryptionMethodGenerator + extends PublicKeyKeyEncryptionMethodGenerator +{ + private SecureRandom random; + private BcPGPKeyConverter keyConverter = new BcPGPKeyConverter(); + + /** + * Create a public key encryption method generator with the method to be based on the passed in key. + * + * @param key the public key to use for encryption. + */ + public BcPublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) + { + super(key); + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current generator. + */ + public BcPublicKeyKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo) + throws PGPException + { + try + { + AsymmetricBlockCipher c = BcImplProvider.createPublicKeyCipher(pubKey.getAlgorithm()); + + AsymmetricKeyParameter key = keyConverter.getPublicKey(pubKey); + + if (random == null) + { + random = new SecureRandom(); + } + + c.init(true, new ParametersWithRandom(key, random)); + + return c.processBlock(sessionInfo, 0, sessionInfo.length); + } + catch (InvalidCipherTextException e) + { + throw new PGPException("exception encrypting session info: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java new file mode 100644 index 000000000..c3be8c832 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/BcUtil.java @@ -0,0 +1,75 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.InputStream; + +import org.spongycastle.crypto.BlockCipher; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.io.CipherInputStream; +import org.spongycastle.crypto.modes.CFBBlockCipher; +import org.spongycastle.crypto.modes.OpenPGPCFBBlockCipher; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +class BcUtil +{ + static BufferedBlockCipher createStreamCipher(boolean forEncryption, BlockCipher engine, boolean withIntegrityPacket, byte[] key) + { + BufferedBlockCipher c; + + if (withIntegrityPacket) + { + c = new BufferedBlockCipher(new CFBBlockCipher(engine, engine.getBlockSize() * 8)); + } + else + { + c = new BufferedBlockCipher(new OpenPGPCFBBlockCipher(engine)); + } + + KeyParameter keyParameter = new KeyParameter(key); + + if (withIntegrityPacket) + { + c.init(forEncryption, new ParametersWithIV(keyParameter, new byte[engine.getBlockSize()])); + } + else + { + c.init(forEncryption, keyParameter); + } + + return c; + } + + public static PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, BlockCipher engine, byte[] key) + { + final BufferedBlockCipher c = createStreamCipher(false, engine, withIntegrityPacket, key); + + return new PGPDataDecryptor() + { + public InputStream getInputStream(InputStream in) + { + return new CipherInputStream(in, c); + } + + public int getBlockSize() + { + return c.getBlockSize(); + } + + public PGPDigestCalculator getIntegrityCalculator() + { + return new SHA1PGPDigestCalculator(); + } + }; + } + + public static BufferedBlockCipher createSymmetricKeyWrapper(boolean forEncryption, BlockCipher engine, byte[] key, byte[] iv) + { + BufferedBlockCipher c = new BufferedBlockCipher(new CFBBlockCipher(engine, engine.getBlockSize() * 8)); + + c.init(forEncryption, new ParametersWithIV(new KeyParameter(key), iv)); + + return c; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java new file mode 100644 index 000000000..15572a398 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SHA1PGPDigestCalculator.java @@ -0,0 +1,68 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +class SHA1PGPDigestCalculator + implements PGPDigestCalculator +{ + private Digest digest = new SHA1Digest(); + + public int getAlgorithm() + { + return HashAlgorithmTags.SHA1; + } + + public OutputStream getOutputStream() + { + return new DigestOutputStream(digest); + } + + public byte[] getDigest() + { + byte[] d = new byte[digest.getDigestSize()]; + + digest.doFinal(d, 0); + + return d; + } + + public void reset() + { + digest.reset(); + } + + private class DigestOutputStream + extends OutputStream + { + private Digest dig; + + DigestOutputStream(Digest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes, 0, bytes.length); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java new file mode 100644 index 000000000..cdc2d7e3e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/bc/SignerOutputStream.java @@ -0,0 +1,35 @@ +package org.spongycastle.openpgp.operator.bc; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.crypto.Signer; + +class SignerOutputStream + extends OutputStream +{ + private Signer sig; + + SignerOutputStream(Signer sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + sig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + sig.update(bytes, 0, bytes.length); + } + + public void write(int b) + throws IOException + { + sig.update((byte)b); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java new file mode 100644 index 000000000..719c319d1 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaKeyFingerprintCalculator.java @@ -0,0 +1,72 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; + +public class JcaKeyFingerprintCalculator + implements KeyFingerPrintCalculator +{ + public byte[] calculateFingerprint(PublicKeyPacket publicPk) + throws PGPException + { + BCPGKey key = publicPk.getKey(); + + if (publicPk.getVersion() <= 3) + { + RSAPublicBCPGKey rK = (RSAPublicBCPGKey)key; + + try + { + MessageDigest digest = MessageDigest.getInstance("MD5"); + + byte[] bytes = new MPInteger(rK.getModulus()).getEncoded(); + digest.update(bytes, 2, bytes.length - 2); + + bytes = new MPInteger(rK.getPublicExponent()).getEncoded(); + digest.update(bytes, 2, bytes.length - 2); + + return digest.digest(); + } + catch (NoSuchAlgorithmException e) + { + throw new PGPException("can't find MD5", e); + } + catch (IOException e) + { + throw new PGPException("can't encode key components: " + e.getMessage(), e); + } + } + else + { + try + { + byte[] kBytes = publicPk.getEncodedContents(); + + MessageDigest digest = MessageDigest.getInstance("SHA1"); + + digest.update((byte)0x99); + digest.update((byte)(kBytes.length >> 8)); + digest.update((byte)kBytes.length); + digest.update(kBytes); + + return digest.digest(); + } + catch (NoSuchAlgorithmException e) + { + throw new PGPException("can't find SHA1", e); + } + catch (IOException e) + { + throw new PGPException("can't encode key components: " + e.getMessage(), e); + } + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java new file mode 100644 index 000000000..e57032780 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilder.java @@ -0,0 +1,156 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.operator.PGPContentSigner; +import org.spongycastle.openpgp.operator.PGPContentSignerBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.util.io.TeeOutputStream; + +public class JcaPGPContentSignerBuilder + implements PGPContentSignerBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private int hashAlgorithm; + private SecureRandom random; + private int keyAlgorithm; + + public JcaPGPContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public JcaPGPContentSignerBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public JcaPGPContentSignerBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + keyConverter.setProvider(provider); + digestCalculatorProviderBuilder.setProvider(provider); + + return this; + } + + public JcaPGPContentSignerBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + keyConverter.setProvider(providerName); + digestCalculatorProviderBuilder.setProvider(providerName); + + return this; + } + + public JcaPGPContentSignerBuilder setDigestProvider(Provider provider) + { + digestCalculatorProviderBuilder.setProvider(provider); + + return this; + } + + public JcaPGPContentSignerBuilder setDigestProvider(String providerName) + { + digestCalculatorProviderBuilder.setProvider(providerName); + + return this; + } + + public PGPContentSigner build(final int signatureType, PGPPrivateKey privateKey) + throws PGPException + { + if (privateKey instanceof JcaPGPPrivateKey) + { + return build(signatureType, privateKey.getKeyID(), ((JcaPGPPrivateKey)privateKey).getPrivateKey()); + } + else + { + return build(signatureType, privateKey.getKeyID(), keyConverter.getPrivateKey(privateKey)); + } + } + + public PGPContentSigner build(final int signatureType, final long keyID, final PrivateKey privateKey) + throws PGPException + { + final PGPDigestCalculator digestCalculator = digestCalculatorProviderBuilder.build().get(hashAlgorithm); + final Signature signature = helper.createSignature(keyAlgorithm, hashAlgorithm); + + try + { + if (random != null) + { + signature.initSign(privateKey, random); + } + else + { + signature.initSign(privateKey); + } + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key.", e); + } + + return new PGPContentSigner() + { + public int getType() + { + return signatureType; + } + + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + public long getKeyID() + { + return keyID; + } + + public OutputStream getOutputStream() + { + return new TeeOutputStream(new SignatureOutputStream(signature), digestCalculator.getOutputStream()); + } + + public byte[] getSignature() + { + try + { + return signature.sign(); + } + catch (SignatureException e) + { // TODO: need a specific runtime exception for PGP operators. + throw new IllegalStateException("unable to create signature"); + } + } + + public byte[] getDigest() + { + return digestCalculator.getDigest(); + } + }; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java new file mode 100644 index 000000000..c933c7447 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPContentVerifierBuilderProvider.java @@ -0,0 +1,112 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.OutputStream; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.Signature; +import java.security.SignatureException; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPContentVerifier; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilder; +import org.spongycastle.openpgp.operator.PGPContentVerifierBuilderProvider; + +public class JcaPGPContentVerifierBuilderProvider + implements PGPContentVerifierBuilderProvider +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + + public JcaPGPContentVerifierBuilderProvider() + { + } + + public JcaPGPContentVerifierBuilderProvider setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + keyConverter.setProvider(provider); + + return this; + } + + public JcaPGPContentVerifierBuilderProvider setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + keyConverter.setProvider(providerName); + + return this; + } + + public PGPContentVerifierBuilder get(int keyAlgorithm, int hashAlgorithm) + throws PGPException + { + return new JcaPGPContentVerifierBuilder(keyAlgorithm, hashAlgorithm); + } + + private class JcaPGPContentVerifierBuilder + implements PGPContentVerifierBuilder + { + private int hashAlgorithm; + private int keyAlgorithm; + + public JcaPGPContentVerifierBuilder(int keyAlgorithm, int hashAlgorithm) + { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public PGPContentVerifier build(final PGPPublicKey publicKey) + throws PGPException + { + final Signature signature = helper.createSignature(keyAlgorithm, hashAlgorithm); + + try + { + signature.initVerify(keyConverter.getPublicKey(publicKey)); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key.", e); + } + + return new PGPContentVerifier() + { + public int getHashAlgorithm() + { + return hashAlgorithm; + } + + public int getKeyAlgorithm() + { + return keyAlgorithm; + } + + public long getKeyID() + { + return publicKey.getKeyID(); + } + + public boolean verify(byte[] expected) + { + try + { + return signature.verify(expected); + } + catch (SignatureException e) + { // TODO: need a specific runtime exception for PGP operators. + throw new IllegalStateException("unable to verify signature"); + } + } + + public OutputStream getOutputStream() + { + return new SignatureOutputStream(signature); + } + }; + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java new file mode 100644 index 000000000..a70919cb8 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPDigestCalculatorProviderBuilder.java @@ -0,0 +1,119 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.Provider; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class JcaPGPDigestCalculatorProviderBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + + public JcaPGPDigestCalculatorProviderBuilder() + { + } + + public JcaPGPDigestCalculatorProviderBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcaPGPDigestCalculatorProviderBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public PGPDigestCalculatorProvider build() + throws PGPException + { + return new PGPDigestCalculatorProvider() + { + public PGPDigestCalculator get(final int algorithm) + throws PGPException + { + final DigestOutputStream stream; + final MessageDigest dig; + + try + { + dig = helper.createDigest(algorithm); + + stream = new DigestOutputStream(dig); + } + catch (GeneralSecurityException e) + { + throw new PGPException("exception on setup: " + e, e); + } + + return new PGPDigestCalculator() + { + public int getAlgorithm() + { + return algorithm; + } + + public OutputStream getOutputStream() + { + return stream; + } + + public byte[] getDigest() + { + return stream.getDigest(); + } + + public void reset() + { + dig.reset(); + } + }; + } + }; + } + + private class DigestOutputStream + extends OutputStream + { + private MessageDigest dig; + + DigestOutputStream(MessageDigest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + + byte[] getDigest() + { + return dig.digest(); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java new file mode 100644 index 000000000..2f6e3c031 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyConverter.java @@ -0,0 +1,373 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.Date; + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.ASN1OctetString; +import org.spongycastle.asn1.DEROctetString; +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9ECPoint; +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.DSAPublicBCPGKey; +import org.spongycastle.bcpg.DSASecretBCPGKey; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.ECDSAPublicBCPGKey; +import org.spongycastle.bcpg.ECSecretBCPGKey; +import org.spongycastle.bcpg.ElGamalPublicBCPGKey; +import org.spongycastle.bcpg.ElGamalSecretBCPGKey; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.bcpg.RSAPublicBCPGKey; +import org.spongycastle.bcpg.RSASecretBCPGKey; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.jce.interfaces.ElGamalPrivateKey; +import org.spongycastle.jce.interfaces.ElGamalPublicKey; +import org.spongycastle.jce.spec.ECNamedCurveSpec; +import org.spongycastle.jce.spec.ElGamalParameterSpec; +import org.spongycastle.jce.spec.ElGamalPrivateKeySpec; +import org.spongycastle.jce.spec.ElGamalPublicKeySpec; +import org.spongycastle.openpgp.PGPAlgorithmParameters; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKdfParameters; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator; + +public class JcaPGPKeyConverter +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private KeyFingerPrintCalculator fingerPrintCalculator = new JcaKeyFingerprintCalculator(); + + public JcaPGPKeyConverter setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcaPGPKeyConverter setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public PublicKey getPublicKey(PGPPublicKey publicKey) + throws PGPException + { + KeyFactory fact; + + PublicKeyPacket publicPk = publicKey.getPublicKeyPacket(); + + try + { + switch (publicPk.getAlgorithm()) + { + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + RSAPublicBCPGKey rsaK = (RSAPublicBCPGKey)publicPk.getKey(); + RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsaK.getModulus(), rsaK.getPublicExponent()); + + fact = helper.createKeyFactory("RSA"); + + return fact.generatePublic(rsaSpec); + case PublicKeyAlgorithmTags.DSA: + DSAPublicBCPGKey dsaK = (DSAPublicBCPGKey)publicPk.getKey(); + DSAPublicKeySpec dsaSpec = new DSAPublicKeySpec(dsaK.getY(), dsaK.getP(), dsaK.getQ(), dsaK.getG()); + + fact = helper.createKeyFactory("DSA"); + + return fact.generatePublic(dsaSpec); + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + ElGamalPublicBCPGKey elK = (ElGamalPublicBCPGKey)publicPk.getKey(); + ElGamalPublicKeySpec elSpec = new ElGamalPublicKeySpec(elK.getY(), new ElGamalParameterSpec(elK.getP(), elK.getG())); + + fact = helper.createKeyFactory("ElGamal"); + + return fact.generatePublic(elSpec); + case PublicKeyAlgorithmTags.EC: + ECDHPublicBCPGKey ecdhK = (ECDHPublicBCPGKey)publicPk.getKey(); + ECPublicKeySpec ecDhSpec = new ECPublicKeySpec( + new java.security.spec.ECPoint(ecdhK.getPoint().getAffineXCoord().toBigInteger(), ecdhK.getPoint().getAffineYCoord().toBigInteger()), + convertX9Parameters(ecdhK.getCurveOID(), NISTNamedCurves.getByOID(ecdhK.getCurveOID()))); + fact = helper.createKeyFactory("ECDH"); + + return fact.generatePublic(ecDhSpec); + case PublicKeyAlgorithmTags.ECDSA: + ECDSAPublicBCPGKey ecdsaK = (ECDSAPublicBCPGKey)publicPk.getKey(); + ECPublicKeySpec ecDsaSpec = new ECPublicKeySpec( + new java.security.spec.ECPoint(ecdsaK.getPoint().getAffineXCoord().toBigInteger(), ecdsaK.getPoint().getAffineYCoord().toBigInteger()), + convertX9Parameters(ecdsaK.getCurveOID(), NISTNamedCurves.getByOID(ecdsaK.getCurveOID()))); + fact = helper.createKeyFactory("ECDSA"); + + return fact.generatePublic(ecDsaSpec); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("exception constructing public key", e); + } + } + + /** + * Create a PGPPublicKey from the passed in JCA one. + * <p/> + * Note: the time passed in affects the value of the key's keyID, so you probably only want + * to do this once for a JCA key, or make sure you keep track of the time you used. + * + * @param algorithm asymmetric algorithm type representing the public key. + * @param algorithmParameters additional parameters to be stored against the public key. + * @param pubKey actual public key to associate. + * @param time date of creation. + * @throws PGPException on key creation problem. + */ + public PGPPublicKey getPGPPublicKey(int algorithm, PGPAlgorithmParameters algorithmParameters, PublicKey pubKey, Date time) + throws PGPException + { + BCPGKey bcpgKey; + + if (pubKey instanceof RSAPublicKey) + { + RSAPublicKey rK = (RSAPublicKey)pubKey; + + bcpgKey = new RSAPublicBCPGKey(rK.getModulus(), rK.getPublicExponent()); + } + else if (pubKey instanceof DSAPublicKey) + { + DSAPublicKey dK = (DSAPublicKey)pubKey; + DSAParams dP = dK.getParams(); + + bcpgKey = new DSAPublicBCPGKey(dP.getP(), dP.getQ(), dP.getG(), dK.getY()); + } + else if (pubKey instanceof ElGamalPublicKey) + { + ElGamalPublicKey eK = (ElGamalPublicKey)pubKey; + ElGamalParameterSpec eS = eK.getParameters(); + + bcpgKey = new ElGamalPublicBCPGKey(eS.getP(), eS.getG(), eK.getY()); + } + else if (pubKey instanceof ECPublicKey) + { + SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()); + + // TODO: should probably match curve by comparison as well + ASN1ObjectIdentifier curveOid = ASN1ObjectIdentifier.getInstance(keyInfo.getAlgorithm().getParameters()); + + X9ECParameters params = NISTNamedCurves.getByOID(curveOid); + + ASN1OctetString key = new DEROctetString(keyInfo.getPublicKeyData().getBytes()); + X9ECPoint derQ = new X9ECPoint(params.getCurve(), key); + + if (algorithm == PGPPublicKey.EC) + { + PGPKdfParameters kdfParams = (PGPKdfParameters)algorithmParameters; + if (kdfParams == null) + { + // We default to these as they are specified as mandatory in RFC 6631. + kdfParams = new PGPKdfParameters(HashAlgorithmTags.SHA256, SymmetricKeyAlgorithmTags.AES_128); + } + bcpgKey = new ECDHPublicBCPGKey(curveOid, derQ.getPoint(), kdfParams.getHashAlgorithm(), kdfParams.getSymmetricWrapAlgorithm()); + } + else + { + bcpgKey = new ECDSAPublicBCPGKey(curveOid, derQ.getPoint()); + } + } + else + { + throw new PGPException("unknown key class"); + } + + return new PGPPublicKey(new PublicKeyPacket(algorithm, time, bcpgKey), fingerPrintCalculator); + } + + /** + * Create a PGPPublicKey from the passed in JCA one. + * <p/> + * Note: the time passed in affects the value of the key's keyID, so you probably only want + * to do this once for a JCA key, or make sure you keep track of the time you used. + * + * @param algorithm asymmetric algorithm type representing the public key. + * @param pubKey actual public key to associate. + * @param time date of creation. + * @throws PGPException on key creation problem. + */ + public PGPPublicKey getPGPPublicKey(int algorithm, PublicKey pubKey, Date time) + throws PGPException + { + return getPGPPublicKey(algorithm, null, pubKey, time); + } + + public PrivateKey getPrivateKey(PGPPrivateKey privKey) + throws PGPException + { + if (privKey instanceof JcaPGPPrivateKey) + { + return ((JcaPGPPrivateKey)privKey).getPrivateKey(); + } + + PublicKeyPacket pubPk = privKey.getPublicKeyPacket(); + BCPGKey privPk = privKey.getPrivateKeyDataPacket(); + + try + { + KeyFactory fact; + + switch (pubPk.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + case PGPPublicKey.RSA_SIGN: + RSAPublicBCPGKey rsaPub = (RSAPublicBCPGKey)pubPk.getKey(); + RSASecretBCPGKey rsaPriv = (RSASecretBCPGKey)privPk; + RSAPrivateCrtKeySpec rsaPrivSpec = new RSAPrivateCrtKeySpec( + rsaPriv.getModulus(), + rsaPub.getPublicExponent(), + rsaPriv.getPrivateExponent(), + rsaPriv.getPrimeP(), + rsaPriv.getPrimeQ(), + rsaPriv.getPrimeExponentP(), + rsaPriv.getPrimeExponentQ(), + rsaPriv.getCrtCoefficient()); + + fact = helper.createKeyFactory("RSA"); + + return fact.generatePrivate(rsaPrivSpec); + case PGPPublicKey.DSA: + DSAPublicBCPGKey dsaPub = (DSAPublicBCPGKey)pubPk.getKey(); + DSASecretBCPGKey dsaPriv = (DSASecretBCPGKey)privPk; + DSAPrivateKeySpec dsaPrivSpec = + new DSAPrivateKeySpec(dsaPriv.getX(), dsaPub.getP(), dsaPub.getQ(), dsaPub.getG()); + + fact = helper.createKeyFactory("DSA"); + + return fact.generatePrivate(dsaPrivSpec); + case PublicKeyAlgorithmTags.ECDH: + ECDHPublicBCPGKey ecdhPub = (ECDHPublicBCPGKey)pubPk.getKey(); + ECSecretBCPGKey ecdhK = (ECSecretBCPGKey)privPk; + ECPrivateKeySpec ecDhSpec = new ECPrivateKeySpec( + ecdhK.getX(), + convertX9Parameters(ecdhPub.getCurveOID(), NISTNamedCurves.getByOID(ecdhPub.getCurveOID()))); + fact = helper.createKeyFactory("ECDH"); + + return fact.generatePrivate(ecDhSpec); + case PublicKeyAlgorithmTags.ECDSA: + ECDSAPublicBCPGKey ecdsaPub = (ECDSAPublicBCPGKey)pubPk.getKey(); + ECSecretBCPGKey ecdsaK = (ECSecretBCPGKey)privPk; + ECPrivateKeySpec ecDsaSpec = new ECPrivateKeySpec( + ecdsaK.getX(), + convertX9Parameters(ecdsaPub.getCurveOID(), NISTNamedCurves.getByOID(ecdsaPub.getCurveOID()))); + fact = helper.createKeyFactory("ECDSA"); + + return fact.generatePrivate(ecDsaSpec); + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPublicBCPGKey elPub = (ElGamalPublicBCPGKey)pubPk.getKey(); + ElGamalSecretBCPGKey elPriv = (ElGamalSecretBCPGKey)privPk; + ElGamalPrivateKeySpec elSpec = new ElGamalPrivateKeySpec(elPriv.getX(), new ElGamalParameterSpec(elPub.getP(), elPub.getG())); + + fact = helper.createKeyFactory("ElGamal"); + + return fact.generatePrivate(elSpec); + default: + throw new PGPException("unknown public key algorithm encountered"); + } + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception constructing key", e); + } + } + + /** + * Convert a PrivateKey into a PGPPrivateKey. + * + * @param pub the corresponding PGPPublicKey to privKey. + * @param privKey the private key for the key in pub. + * @return a PGPPrivateKey + * @throws PGPException + */ + public PGPPrivateKey getPGPPrivateKey(PGPPublicKey pub, PrivateKey privKey) + throws PGPException + { + BCPGKey privPk; + + switch (pub.getAlgorithm()) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_SIGN: + case PGPPublicKey.RSA_GENERAL: + RSAPrivateCrtKey rsK = (RSAPrivateCrtKey)privKey; + + privPk = new RSASecretBCPGKey(rsK.getPrivateExponent(), rsK.getPrimeP(), rsK.getPrimeQ()); + break; + case PGPPublicKey.DSA: + DSAPrivateKey dsK = (DSAPrivateKey)privKey; + + privPk = new DSASecretBCPGKey(dsK.getX()); + break; + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + ElGamalPrivateKey esK = (ElGamalPrivateKey)privKey; + + privPk = new ElGamalSecretBCPGKey(esK.getX()); + break; + case PGPPublicKey.EC: + case PGPPublicKey.ECDSA: + ECPrivateKey ecK = (ECPrivateKey)privKey; + + privPk = new ECSecretBCPGKey(ecK.getS()); + break; + default: + throw new PGPException("unknown key class"); + } + + return new PGPPrivateKey(pub.getKeyID(), pub.getPublicKeyPacket(), privPk); + } + + private ECParameterSpec convertX9Parameters(ASN1ObjectIdentifier curveOid, X9ECParameters curveParameters) + { + return new ECNamedCurveSpec(curveOid.getId(), + curveParameters.getCurve(), + curveParameters.getG(), + curveParameters.getN(), + curveParameters.getH(), + curveParameters.getSeed()); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java new file mode 100644 index 000000000..b32d00d74 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPKeyPair.java @@ -0,0 +1,48 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Date; + +import org.spongycastle.openpgp.PGPAlgorithmParameters; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyPair; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; + +public class JcaPGPKeyPair + extends PGPKeyPair +{ + private static PGPPublicKey getPublicKey(int algorithm, PublicKey pubKey, Date date) + throws PGPException + { + return new JcaPGPKeyConverter().getPGPPublicKey(algorithm, pubKey, date); + } + + private static PGPPublicKey getPublicKey(int algorithm, PGPAlgorithmParameters algorithmParameters, PublicKey pubKey, Date date) + throws PGPException + { + return new JcaPGPKeyConverter().getPGPPublicKey(algorithm, algorithmParameters, pubKey, date); + } + + private static PGPPrivateKey getPrivateKey(PGPPublicKey pub, PrivateKey privKey) + throws PGPException + { + return new JcaPGPKeyConverter().getPGPPrivateKey(pub, privKey); + } + + public JcaPGPKeyPair(int algorithm, KeyPair keyPair, Date date) + throws PGPException + { + this.pub = getPublicKey(algorithm, keyPair.getPublic(), date); + this.priv = getPrivateKey(this.pub, keyPair.getPrivate()); + } + + public JcaPGPKeyPair(int algorithm, PGPAlgorithmParameters parameters, KeyPair keyPair, Date date) + throws PGPException + { + this.pub = getPublicKey(algorithm, parameters, keyPair.getPublic(), date); + this.priv = getPrivateKey(this.pub, keyPair.getPrivate()); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java new file mode 100644 index 000000000..a22f4561e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcaPGPPrivateKey.java @@ -0,0 +1,34 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.PrivateKey; + +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; + +/** + * A JCA PrivateKey carrier. Use this one if you're dealing with a hardware adapter. + */ +public class JcaPGPPrivateKey + extends PGPPrivateKey +{ + private final PrivateKey privateKey; + + public JcaPGPPrivateKey(long keyID, PrivateKey privateKey) + { + super(keyID, null, null); + + this.privateKey = privateKey; + } + + public JcaPGPPrivateKey(PGPPublicKey pubKey, PrivateKey privateKey) + { + super(pubKey.getKeyID(), pubKey.getPublicKeyPacket(), null); + + this.privateKey = privateKey; + } + + public PrivateKey getPrivateKey() + { + return privateKey; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java new file mode 100644 index 000000000..7f18d34b8 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java @@ -0,0 +1,99 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class JcePBEDataDecryptorFactoryBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private PGPDigestCalculatorProvider calculatorProvider; + + /** + * Base constructor. + * + * @param calculatorProvider a digest calculator provider to provide calculators to support the key generation calculation required. + */ + public JcePBEDataDecryptorFactoryBuilder(PGPDigestCalculatorProvider calculatorProvider) + { + this.calculatorProvider = calculatorProvider; + } + + /** + * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param provider provider object for cryptographic primitives. + * @return the current builder. + */ + public JcePBEDataDecryptorFactoryBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + /** + * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param providerName the name of the provider to reference for cryptographic primitives. + * @return the current builder. + */ + public JcePBEDataDecryptorFactoryBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public PBEDataDecryptorFactory build(char[] passPhrase) + { + return new PBEDataDecryptorFactory(passPhrase, calculatorProvider) + { + public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData) + throws PGPException + { + try + { + if (secKeyData != null && secKeyData.length > 0) + { + String cipherName = PGPUtil.getSymmetricCipherName(keyAlgorithm); + Cipher keyCipher = helper.createCipher(cipherName + "/CFB/NoPadding"); + + keyCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, cipherName), new IvParameterSpec(new byte[keyCipher.getBlockSize()])); + + return keyCipher.doFinal(secKeyData); + } + else + { + byte[] keyBytes = new byte[key.length + 1]; + + keyBytes[0] = (byte)keyAlgorithm; + System.arraycopy(key, 0, keyBytes, 1, key.length); + + return keyBytes; + } + } + catch (Exception e) + { + throw new PGPException("Exception recovering session info", e); + } + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + return helper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + }; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java new file mode 100644 index 000000000..1cb79f769 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBEKeyEncryptionMethodGenerator.java @@ -0,0 +1,132 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +/** + * JCE based generator for password based encryption (PBE) data protection methods. + */ +public class JcePBEKeyEncryptionMethodGenerator + extends PBEKeyEncryptionMethodGenerator +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + + /** + * Create a PBE encryption method generator using the provided calculator for key calculation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kDigestCalculator the digest calculator to use for key calculation. + */ + public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator) + { + super(passPhrase, s2kDigestCalculator); + } + + /** + * Create a PBE encryption method generator using the default SHA-1 digest calculator for key calculation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + */ + public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase) + { + this(passPhrase, new SHA1PGPDigestCalculator()); + } + + /** + * Create a PBE encryption method generator using the provided calculator and S2K count for key calculation. + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kDigestCalculator the digest calculator to use for key calculation. + * @param s2kCount the S2K count to use. + */ + public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, PGPDigestCalculator s2kDigestCalculator, int s2kCount) + { + super(passPhrase, s2kDigestCalculator, s2kCount); + } + + /** + * Create a PBE encryption method generator using the default SHA-1 digest calculator and + * a S2K count other than the default of 0x60 for key calculation + * + * @param passPhrase the passphrase to use as the primary source of key material. + * @param s2kCount the S2K count to use. + */ + public JcePBEKeyEncryptionMethodGenerator(char[] passPhrase, int s2kCount) + { + super(passPhrase, new SHA1PGPDigestCalculator(), s2kCount); + } + + public JcePBEKeyEncryptionMethodGenerator setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcePBEKeyEncryptionMethodGenerator setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current generator. + */ + public PBEKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + super.setSecureRandom(random); + + return this; + } + + protected byte[] encryptSessionInfo(int encAlgorithm, byte[] key, byte[] sessionInfo) + throws PGPException + { + try + { + String cName = PGPUtil.getSymmetricCipherName(encAlgorithm); + Cipher c = helper.createCipher(cName + "/CFB/NoPadding"); + SecretKey sKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm)); + + c.init(Cipher.ENCRYPT_MODE, sKey, new IvParameterSpec(new byte[c.getBlockSize()])); + + return c.doFinal(sessionInfo, 0, sessionInfo.length); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("IV invalid: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("key invalid: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java new file mode 100644 index 000000000..e62e4674e --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyDecryptorBuilder.java @@ -0,0 +1,100 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.IvParameterSpec; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider; + +public class JcePBESecretKeyDecryptorBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private PGPDigestCalculatorProvider calculatorProvider; + + private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder; + + public JcePBESecretKeyDecryptorBuilder() + { + this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + } + + public JcePBESecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider) + { + this.calculatorProvider = calculatorProvider; + } + + public JcePBESecretKeyDecryptorBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + if (calculatorProviderBuilder != null) + { + calculatorProviderBuilder.setProvider(provider); + } + + return this; + } + + public JcePBESecretKeyDecryptorBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + if (calculatorProviderBuilder != null) + { + calculatorProviderBuilder.setProvider(providerName); + } + + return this; + } + + public PBESecretKeyDecryptor build(char[] passPhrase) + throws PGPException + { + if (calculatorProvider == null) + { + calculatorProvider = calculatorProviderBuilder.build(); + } + + return new PBESecretKeyDecryptor(passPhrase, calculatorProvider) + { + public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding"); + + c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv)); + + return c.doFinal(keyData, keyOff, keyLen); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("invalid parameter: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + } + }; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java new file mode 100644 index 000000000..7b18790ab --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePBESecretKeyEncryptorBuilder.java @@ -0,0 +1,180 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.IvParameterSpec; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +public class JcePBESecretKeyEncryptorBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private int encAlgorithm; + private PGPDigestCalculator s2kDigestCalculator; + private SecureRandom random; + private int s2kCount = 0x60; + + public JcePBESecretKeyEncryptorBuilder(int encAlgorithm) + { + this(encAlgorithm, new SHA1PGPDigestCalculator()); + } + + /** + * Create a SecretKeyEncryptorBuilder with the S2K count different to the default of 0x60. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kCount iteration count to use for S2K function. + */ + public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, int s2kCount) + { + this(encAlgorithm, new SHA1PGPDigestCalculator(), s2kCount); + } + + /** + * Create a builder which will make encryptors using the passed in digest calculator. If a MD5 calculator is + * passed in the builder will assume the encryptors are for use with version 3 keys. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kDigestCalculator digest calculator to use. + */ + public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator) + { + this(encAlgorithm, s2kDigestCalculator, 0x60); + } + + /** + * Create an SecretKeyEncryptorBuilder with the S2k count different to the default of 0x60, and the S2K digest + * different from SHA-1. + * + * @param encAlgorithm encryption algorithm to use. + * @param s2kDigestCalculator digest calculator to use. + * @param s2kCount iteration count to use for S2K function. + */ + public JcePBESecretKeyEncryptorBuilder(int encAlgorithm, PGPDigestCalculator s2kDigestCalculator, int s2kCount) + { + this.encAlgorithm = encAlgorithm; + this.s2kDigestCalculator = s2kDigestCalculator; + + if (s2kCount < 0 || s2kCount > 0xff) + { + throw new IllegalArgumentException("s2KCount value outside of range 0 to 255."); + } + + this.s2kCount = s2kCount; + } + + public JcePBESecretKeyEncryptorBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcePBESecretKeyEncryptorBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current builder. + */ + public JcePBESecretKeyEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public PBESecretKeyEncryptor build(char[] passPhrase) + { + if (random == null) + { + random = new SecureRandom(); + } + + return new PBESecretKeyEncryptor(encAlgorithm, s2kDigestCalculator, s2kCount, random, passPhrase) + { + private Cipher c; + private byte[] iv; + + public byte[] encryptKeyData(byte[] key, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + c = helper.createCipher(PGPUtil.getSymmetricCipherName(this.encAlgorithm) + "/CFB/NoPadding"); + + c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(this.encAlgorithm, key), this.random); + + iv = c.getIV(); + + return c.doFinal(keyData, keyOff, keyLen); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + } + + public byte[] encryptKeyData(byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen) + throws PGPException + { + try + { + c = helper.createCipher(PGPUtil.getSymmetricCipherName(this.encAlgorithm) + "/CFB/NoPadding"); + + c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(this.encAlgorithm, key), new IvParameterSpec(iv)); + + this.iv = iv; + + return c.doFinal(keyData, keyOff, keyLen); + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("invalid iv: " + e.getMessage(), e); + } + } + + public byte[] getCipherIV() + { + return iv; + } + }; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java new file mode 100644 index 000000000..ba6e793ba --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePGPDataEncryptorBuilder.java @@ -0,0 +1,146 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherOutputStream; +import javax.crypto.spec.IvParameterSpec; + +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.PGPDataEncryptor; +import org.spongycastle.openpgp.operator.PGPDataEncryptorBuilder; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +public class JcePGPDataEncryptorBuilder + implements PGPDataEncryptorBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private SecureRandom random; + private boolean withIntegrityPacket; + private int encAlgorithm; + + public JcePGPDataEncryptorBuilder(int encAlgorithm) + { + this.encAlgorithm = encAlgorithm; + + if (encAlgorithm == 0) + { + throw new IllegalArgumentException("null cipher specified"); + } + } + + /** + * Determine whether or not the resulting encrypted data will be protected using an integrity packet. + * + * @param withIntegrityPacket true if an integrity packet is to be included, false otherwise. + * @return the current builder. + */ + public JcePGPDataEncryptorBuilder setWithIntegrityPacket(boolean withIntegrityPacket) + { + this.withIntegrityPacket = withIntegrityPacket; + + return this; + } + + public JcePGPDataEncryptorBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcePGPDataEncryptorBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current builder. + */ + public JcePGPDataEncryptorBuilder setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + public int getAlgorithm() + { + return encAlgorithm; + } + + public SecureRandom getSecureRandom() + { + if (random == null) + { + random = new SecureRandom(); + } + + return random; + } + + public PGPDataEncryptor build(byte[] keyBytes) + throws PGPException + { + return new MyPGPDataEncryptor(keyBytes); + } + + private class MyPGPDataEncryptor + implements PGPDataEncryptor + { + private final Cipher c; + + MyPGPDataEncryptor(byte[] keyBytes) + throws PGPException + { + c = helper.createStreamCipher(encAlgorithm, withIntegrityPacket); + + byte[] iv = new byte[c.getBlockSize()]; + + try + { + c.init(Cipher.ENCRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, keyBytes), new IvParameterSpec(iv)); + } + catch (InvalidKeyException e) + { + throw new PGPException("invalid key: " + e.getMessage(), e); + } + catch (InvalidAlgorithmParameterException e) + { + throw new PGPException("imvalid algorithm parameter: " + e.getMessage(), e); + } + } + + public OutputStream getOutputStream(OutputStream out) + { + return new CipherOutputStream(out, c); + } + + public PGPDigestCalculator getIntegrityCalculator() + { + if (withIntegrityPacket) + { + return new SHA1PGPDigestCalculator(); + } + + return null; + } + + public int getBlockSize() + { + return c.getBlockSize(); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java new file mode 100644 index 000000000..344849fef --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java @@ -0,0 +1,241 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.bcpg.BCPGKey; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.ECSecretBCPGKey; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyPacket; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.jce.interfaces.ElGamalKey; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.spongycastle.openpgp.operator.RFC6637KDFCalculator; + +public class JcePublicKeyDataDecryptorFactoryBuilder +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper()); + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator(); + + public JcePublicKeyDataDecryptorFactoryBuilder() + { + } + + /** + * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param provider provider object for cryptographic primitives. + * @return the current builder. + */ + public JcePublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + keyConverter.setProvider(provider); + this.contentHelper = helper; + + return this; + } + + /** + * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param providerName the name of the provider to reference for cryptographic primitives. + * @return the current builder. + */ + public JcePublicKeyDataDecryptorFactoryBuilder setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + keyConverter.setProvider(providerName); + this.contentHelper = helper; + + return this; + } + + public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider) + { + this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + return this; + } + + public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName) + { + this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + return this; + } + + public PublicKeyDataDecryptorFactory build(final PrivateKey privKey) + { + return new PublicKeyDataDecryptorFactory() + { + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) + throws PGPException + { + if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) + { + throw new PGPException("ECDH requires use of PGPPrivateKey for decryption"); + } + return decryptSessionData(keyAlgorithm, privKey, secKeyData); + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + }; + } + + public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey) + { + return new PublicKeyDataDecryptorFactory() + { + public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) + throws PGPException + { + if (keyAlgorithm == PublicKeyAlgorithmTags.ECDH) + { + return decryptSessionData(privKey.getPrivateKeyDataPacket(), privKey.getPublicKeyPacket(), secKeyData); + } + + return decryptSessionData(keyAlgorithm, keyConverter.getPrivateKey(privKey), secKeyData); + } + + public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); + } + }; + } + + private byte[] decryptSessionData(BCPGKey privateKeyPacket, PublicKeyPacket pubKeyData, byte[][] secKeyData) + throws PGPException + { + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKeyData.getKey(); + X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); + ECDomainParameters ecParams = new ECDomainParameters(x9Params.getCurve(), x9Params.getG(), x9Params.getN()); + + byte[] enc = secKeyData[0]; + + int pLen = ((((enc[0] & 0xff) << 8) + (enc[1] & 0xff)) + 7) / 8; + byte[] pEnc = new byte[pLen]; + + System.arraycopy(enc, 2, pEnc, 0, pLen); + + byte[] keyEnc = new byte[enc[pLen + 2]]; + + System.arraycopy(enc, 2 + pLen + 1, keyEnc, 0, keyEnc.length); + + Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm()); + + ECPoint S = x9Params.getCurve().decodePoint(pEnc).multiply(((ECSecretBCPGKey)privateKeyPacket).getX()).normalize(); + + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); + + Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, fingerprintCalculator.calculateFingerprint(pubKeyData)), "AESWrap"); + + try + { + c.init(Cipher.UNWRAP_MODE, key); + + Key paddedSessionKey = c.unwrap(keyEnc, "Session", Cipher.SECRET_KEY); + + return PGPUtil.unpadSessionData(paddedSessionKey.getEncoded()); + } + catch (InvalidKeyException e) + { + throw new PGPException("error setting asymmetric cipher", e); + } + catch (NoSuchAlgorithmException e) + { + throw new PGPException("error setting asymmetric cipher", e); + } + } + + private byte[] decryptSessionData(int keyAlgorithm, PrivateKey privKey, byte[][] secKeyData) + throws PGPException + { + Cipher c1 = helper.createPublicKeyCipher(keyAlgorithm); + + try + { + c1.init(Cipher.DECRYPT_MODE, privKey); + } + catch (InvalidKeyException e) + { + throw new PGPException("error setting asymmetric cipher", e); + } + + if (keyAlgorithm == PGPPublicKey.RSA_ENCRYPT + || keyAlgorithm == PGPPublicKey.RSA_GENERAL) + { + byte[] bi = secKeyData[0]; // encoded MPI + + c1.update(bi, 2, bi.length - 2); + } + else + { + ElGamalKey k = (ElGamalKey)privKey; + int size = (k.getParameters().getP().bitLength() + 7) / 8; + byte[] tmp = new byte[size]; + + byte[] bi = secKeyData[0]; // encoded MPI + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... + { + c1.update(bi, 3, bi.length - 3); + } + else + { + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.update(tmp); + } + + bi = secKeyData[1]; // encoded MPI + for (int i = 0; i != tmp.length; i++) + { + tmp[i] = 0; + } + + if (bi.length - 2 > size) // leading Zero? Shouldn't happen but... + { + c1.update(bi, 3, bi.length - 3); + } + else + { + System.arraycopy(bi, 2, tmp, tmp.length - (bi.length - 2), bi.length - 2); + c1.update(tmp); + } + } + + try + { + return c1.doFinal(); + } + catch (Exception e) + { + throw new PGPException("exception decrypting session data", e); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java new file mode 100644 index 000000000..5a2708cf8 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/JcePublicKeyKeyEncryptionMethodGenerator.java @@ -0,0 +1,165 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.asn1.nist.NISTNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.bcpg.ECDHPublicBCPGKey; +import org.spongycastle.bcpg.MPInteger; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.crypto.EphemeralKeyPair; +import org.spongycastle.crypto.KeyEncoder; +import org.spongycastle.crypto.generators.ECKeyPairGenerator; +import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator; +import org.spongycastle.crypto.params.AsymmetricKeyParameter; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECKeyGenerationParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.jcajce.DefaultJcaJceHelper; +import org.spongycastle.jcajce.NamedJcaJceHelper; +import org.spongycastle.jcajce.ProviderJcaJceHelper; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator; +import org.spongycastle.openpgp.operator.RFC6637KDFCalculator; + +public class JcePublicKeyKeyEncryptionMethodGenerator + extends PublicKeyKeyEncryptionMethodGenerator +{ + private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper()); + private SecureRandom random; + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); + + /** + * Create a public key encryption method generator with the method to be based on the passed in key. + * + * @param key the public key to use for encryption. + */ + public JcePublicKeyKeyEncryptionMethodGenerator(PGPPublicKey key) + { + super(key); + } + + public JcePublicKeyKeyEncryptionMethodGenerator setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + + keyConverter.setProvider(provider); + + return this; + } + + public JcePublicKeyKeyEncryptionMethodGenerator setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + + keyConverter.setProvider(providerName); + + return this; + } + + /** + * Provide a user defined source of randomness. + * + * @param random the secure random to be used. + * @return the current generator. + */ + public JcePublicKeyKeyEncryptionMethodGenerator setSecureRandom(SecureRandom random) + { + this.random = random; + + return this; + } + + protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo) + throws PGPException + { + try + { + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.ECDH) + { + ECDHPublicBCPGKey ecKey = (ECDHPublicBCPGKey)pubKey.getPublicKeyPacket().getKey(); + X9ECParameters x9Params = NISTNamedCurves.getByOID(ecKey.getCurveOID()); + ECDomainParameters ecParams = new ECDomainParameters(x9Params.getCurve(), x9Params.getG(), x9Params.getN()); + + // Generate the ephemeral key pair + ECKeyPairGenerator gen = new ECKeyPairGenerator(); + gen.init(new ECKeyGenerationParameters(ecParams, random)); + + EphemeralKeyPairGenerator kGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder() + { + public byte[] getEncoded(AsymmetricKeyParameter keyParameter) + { + return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded(false); + } + }); + + EphemeralKeyPair ephKp = kGen.generate(); + + ECPrivateKeyParameters ephPriv = (ECPrivateKeyParameters)ephKp.getKeyPair().getPrivate(); + + ECPoint S = ecKey.getPoint().multiply(ephPriv.getD()).normalize(); + + RFC6637KDFCalculator rfc6637KDFCalculator = new RFC6637KDFCalculator(digestCalculatorProviderBuilder.build().get(ecKey.getHashAlgorithm()), ecKey.getSymmetricKeyAlgorithm()); + + Key key = new SecretKeySpec(rfc6637KDFCalculator.createKey(ecKey.getCurveOID(), S, pubKey.getFingerprint()), "AESWrap"); + + Cipher c = helper.createKeyWrapper(ecKey.getSymmetricKeyAlgorithm()); + + c.init(Cipher.WRAP_MODE, key, random); + + byte[] paddedSessionData = PGPUtil.padSessionData(sessionInfo); + + byte[] C = c.wrap(new SecretKeySpec(paddedSessionData, PGPUtil.getSymmetricCipherName(sessionInfo[0]))); + byte[] VB = new MPInteger(new BigInteger(1, ephKp.getEncodedPublicKey())).getEncoded(); + + byte[] rv = new byte[VB.length + 1 + C.length]; + + System.arraycopy(VB, 0, rv, 0, VB.length); + rv[VB.length] = (byte)C.length; + System.arraycopy(C, 0, rv, VB.length + 1, C.length); + + return rv; + } + else + { + Cipher c = helper.createPublicKeyCipher(pubKey.getAlgorithm()); + + Key key = keyConverter.getPublicKey(pubKey); + + c.init(Cipher.ENCRYPT_MODE, key, random); + + return c.doFinal(sessionInfo); + } + } + catch (IllegalBlockSizeException e) + { + throw new PGPException("illegal block size: " + e.getMessage(), e); + } + catch (BadPaddingException e) + { + throw new PGPException("bad padding: " + e.getMessage(), e); + } + catch (InvalidKeyException e) + { + throw new PGPException("key invalid: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new PGPException("unable to encode MPI: " + e.getMessage(), e); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java new file mode 100644 index 000000000..134935d31 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/OperatorHelper.java @@ -0,0 +1,196 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.Signature; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.jcajce.JcaJceHelper; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.operator.PGPDataDecryptor; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +class OperatorHelper +{ + private JcaJceHelper helper; + + OperatorHelper(JcaJceHelper helper) + { + this.helper = helper; + } + + MessageDigest createDigest(int algorithm) + throws GeneralSecurityException, PGPException + { + MessageDigest dig; + + dig = helper.createDigest(PGPUtil.getDigestName(algorithm)); + + return dig; + } + + KeyFactory createKeyFactory(String algorithm) + throws GeneralSecurityException, PGPException + { + return helper.createKeyFactory(algorithm); + } + + PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) + throws PGPException + { + try + { + SecretKey secretKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm)); + + final Cipher c = createStreamCipher(encAlgorithm, withIntegrityPacket); + + byte[] iv = new byte[c.getBlockSize()]; + + c.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); + + return new PGPDataDecryptor() + { + public InputStream getInputStream(InputStream in) + { + return new CipherInputStream(in, c); + } + + public int getBlockSize() + { + return c.getBlockSize(); + } + + public PGPDigestCalculator getIntegrityCalculator() + { + return new SHA1PGPDigestCalculator(); + } + }; + } + catch (PGPException e) + { + throw e; + } + catch (Exception e) + { + throw new PGPException("Exception creating cipher", e); + } + } + + Cipher createStreamCipher(int encAlgorithm, boolean withIntegrityPacket) + throws PGPException + { + String mode = (withIntegrityPacket) + ? "CFB" + : "OpenPGPCFB"; + + String cName = PGPUtil.getSymmetricCipherName(encAlgorithm) + + "/" + mode + "/NoPadding"; + + return createCipher(cName); + } + + Cipher createCipher(String cipherName) + throws PGPException + { + try + { + return helper.createCipher(cipherName); + } + catch (GeneralSecurityException e) + { + throw new PGPException("cannot create cipher: " + e.getMessage(), e); + } + } + + Cipher createPublicKeyCipher(int encAlgorithm) + throws PGPException + { + switch (encAlgorithm) + { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + return createCipher("RSA/ECB/PKCS1Padding"); + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: + return createCipher("ElGamal/ECB/PKCS1Padding"); + case PGPPublicKey.DSA: + throw new PGPException("Can't use DSA for encryption."); + case PGPPublicKey.ECDSA: + throw new PGPException("Can't use ECDSA for encryption."); + default: + throw new PGPException("unknown asymmetric algorithm: " + encAlgorithm); + } + } + + Cipher createKeyWrapper(int encAlgorithm) + throws PGPException + { + try + { + switch (encAlgorithm) + { + case SymmetricKeyAlgorithmTags.AES_128: + case SymmetricKeyAlgorithmTags.AES_192: + case SymmetricKeyAlgorithmTags.AES_256: + return helper.createCipher("AESWrap"); + default: + throw new PGPException("unknown wrap algorithm: " + encAlgorithm); + } + } + catch (GeneralSecurityException e) + { + throw new PGPException("cannot create cipher: " + e.getMessage(), e); + } + } + + private Signature createSignature(String cipherName) + throws PGPException + { + try + { + return helper.createSignature(cipherName); + } + catch (GeneralSecurityException e) + { + throw new PGPException("cannot create signature: " + e.getMessage(), e); + } + } + + public Signature createSignature(int keyAlgorithm, int hashAlgorithm) + throws PGPException + { + String encAlg; + + switch (keyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + encAlg = "RSA"; + break; + case PublicKeyAlgorithmTags.DSA: + encAlg = "DSA"; + break; + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases. + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + encAlg = "ElGamal"; + break; + case PublicKeyAlgorithmTags.ECDSA: + encAlg = "ECDSA"; + break; + default: + throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm); + } + + return createSignature(PGPUtil.getDigestName(hashAlgorithm) + "with" + encAlg); + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java new file mode 100644 index 000000000..49b119dda --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/PGPUtil.java @@ -0,0 +1,185 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.PublicKeyAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.openpgp.PGPException; + +/** + * Basic utility class + */ +class PGPUtil +{ + static String getDigestName( + int hashAlgorithm) + throws PGPException + { + switch (hashAlgorithm) + { + case HashAlgorithmTags.SHA1: + return "SHA1"; + case HashAlgorithmTags.MD2: + return "MD2"; + case HashAlgorithmTags.MD5: + return "MD5"; + case HashAlgorithmTags.RIPEMD160: + return "RIPEMD160"; + case HashAlgorithmTags.SHA256: + return "SHA256"; + case HashAlgorithmTags.SHA384: + return "SHA384"; + case HashAlgorithmTags.SHA512: + return "SHA512"; + case HashAlgorithmTags.SHA224: + return "SHA224"; + case HashAlgorithmTags.TIGER_192: + return "TIGER"; + default: + throw new PGPException("unknown hash algorithm tag in getDigestName: " + hashAlgorithm); + } + } + + static String getSignatureName( + int keyAlgorithm, + int hashAlgorithm) + throws PGPException + { + String encAlg; + + switch (keyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + encAlg = "RSA"; + break; + case PublicKeyAlgorithmTags.DSA: + encAlg = "DSA"; + break; + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: // in some malformed cases. + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + encAlg = "ElGamal"; + break; + default: + throw new PGPException("unknown algorithm tag in signature:" + keyAlgorithm); + } + + return getDigestName(hashAlgorithm) + "with" + encAlg; + } + + static String getSymmetricCipherName( + int algorithm) + { + switch (algorithm) + { + case SymmetricKeyAlgorithmTags.NULL: + return null; + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + return "DESEDE"; + case SymmetricKeyAlgorithmTags.IDEA: + return "IDEA"; + case SymmetricKeyAlgorithmTags.CAST5: + return "CAST5"; + case SymmetricKeyAlgorithmTags.BLOWFISH: + return "Blowfish"; + case SymmetricKeyAlgorithmTags.SAFER: + return "SAFER"; + case SymmetricKeyAlgorithmTags.DES: + return "DES"; + case SymmetricKeyAlgorithmTags.AES_128: + return "AES"; + case SymmetricKeyAlgorithmTags.AES_192: + return "AES"; + case SymmetricKeyAlgorithmTags.AES_256: + return "AES"; + case SymmetricKeyAlgorithmTags.TWOFISH: + return "Twofish"; + default: + throw new IllegalArgumentException("unknown symmetric algorithm: " + algorithm); + } + } + + public static SecretKey makeSymmetricKey( + int algorithm, + byte[] keyBytes) + throws PGPException + { + String algName; + + switch (algorithm) + { + case SymmetricKeyAlgorithmTags.TRIPLE_DES: + algName = "DES_EDE"; + break; + case SymmetricKeyAlgorithmTags.IDEA: + algName = "IDEA"; + break; + case SymmetricKeyAlgorithmTags.CAST5: + algName = "CAST5"; + break; + case SymmetricKeyAlgorithmTags.BLOWFISH: + algName = "Blowfish"; + break; + case SymmetricKeyAlgorithmTags.SAFER: + algName = "SAFER"; + break; + case SymmetricKeyAlgorithmTags.DES: + algName = "DES"; + break; + case SymmetricKeyAlgorithmTags.AES_128: + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.AES_192: + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.AES_256: + algName = "AES"; + break; + case SymmetricKeyAlgorithmTags.TWOFISH: + algName = "Twofish"; + break; + default: + throw new PGPException("unknown symmetric algorithm: " + algorithm); + } + + return new SecretKeySpec(keyBytes, algName); + } + + public static byte[] padSessionData(byte[] sessionInfo) + { + byte[] result = new byte[40]; + + System.arraycopy(sessionInfo, 0, result, 0, sessionInfo.length); + + byte padValue = (byte)(result.length -sessionInfo.length); + + for (int i = sessionInfo.length; i != result.length; i++) + { + result[i] = padValue; + } + + return result; + } + + public static byte[] unpadSessionData(byte[] encoded) + throws PGPException + { + byte padValue = encoded[encoded.length - 1]; + + for (int i = encoded.length - padValue; i != encoded.length; i++) + { + if (encoded[i] != padValue) + { + throw new PGPException("bad padding found in session data"); + } + } + + byte[] taggedKey = new byte[encoded.length - padValue]; + + System.arraycopy(encoded, 0, taggedKey, 0, taggedKey.length); + + return taggedKey; + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java new file mode 100644 index 000000000..424770e6b --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SHA1PGPDigestCalculator.java @@ -0,0 +1,81 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.openpgp.operator.PGPDigestCalculator; + +class SHA1PGPDigestCalculator + implements PGPDigestCalculator +{ + private MessageDigest digest; + + SHA1PGPDigestCalculator() + { + try + { + digest = MessageDigest.getInstance("SHA1"); + } + catch (NoSuchAlgorithmException e) + { + throw new IllegalStateException("cannot find SHA-1: " + e.getMessage()); + } + } + + public int getAlgorithm() + { + return HashAlgorithmTags.SHA1; + } + + public OutputStream getOutputStream() + { + return new DigestOutputStream(digest); + } + + public byte[] getDigest() + { + return digest.digest(); + } + + public void reset() + { + digest.reset(); + } + + private class DigestOutputStream + extends OutputStream + { + private MessageDigest dig; + + DigestOutputStream(MessageDigest dig) + { + this.dig = dig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + dig.update(bytes, off, len); + } + + public void write(byte[] bytes) + throws IOException + { + dig.update(bytes); + } + + public void write(int b) + throws IOException + { + dig.update((byte)b); + } + + byte[] getDigest() + { + return dig.digest(); + } + } +} diff --git a/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java new file mode 100644 index 000000000..808de3000 --- /dev/null +++ b/libraries/spongycastle/pg/src/main/java/org/spongycastle/openpgp/operator/jcajce/SignatureOutputStream.java @@ -0,0 +1,56 @@ +package org.spongycastle.openpgp.operator.jcajce; + +import java.io.IOException; +import java.io.OutputStream; +import java.security.Signature; +import java.security.SignatureException; + +class SignatureOutputStream + extends OutputStream +{ + private Signature sig; + + SignatureOutputStream(Signature sig) + { + this.sig = sig; + } + + public void write(byte[] bytes, int off, int len) + throws IOException + { + try + { + sig.update(bytes, off, len); + } + catch (SignatureException e) + { + throw new IOException("signature update caused exception: " + e.getMessage()); + } + } + + public void write(byte[] bytes) + throws IOException + { + try + { + sig.update(bytes); + } + catch (SignatureException e) + { + throw new IOException("signature update caused exception: " + e.getMessage()); + } + } + + public void write(int b) + throws IOException + { + try + { + sig.update((byte)b); + } + catch (SignatureException e) + { + throw new IOException("signature update caused exception: " + e.getMessage()); + } + } +} |