diff --git a/pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java b/pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java index d923019dbe..b00f45fcc4 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/OnePassSignaturePacket.java @@ -1,5 +1,8 @@ package org.bouncycastle.bcpg; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.io.Streams; + import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -9,12 +12,17 @@ public class OnePassSignaturePacket extends ContainedPacket { - private int version; - private int sigType; - private int hashAlgorithm; - private int keyAlgorithm; - private long keyID; - private int isContaining; + public static final int VERSION_3 = 3; + public static final int VERSION_6 = 6; + + private final int version; // v3, v6 + private final int sigType; // v3, v6 + private final int hashAlgorithm; // v3, v6 + private final int keyAlgorithm; // v3, v6 + private final byte[] salt; // v6 + private final long keyID; // v3 + private final byte[] keyFingerprint; // v6 + private final int isContaining; // v3, v6 OnePassSignaturePacket( BCPGInputStream in) @@ -24,19 +32,80 @@ public class OnePassSignaturePacket 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(); - - isContaining = in.read(); + + if (version == VERSION_3) + { + long keyID = 0; + 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(); + this.keyID = keyID; + + isContaining = in.read(); + + this.salt = null; + this.keyFingerprint = null; + } + else if (version == VERSION_6) + { + keyID = 0; + int expectedSaltSize = SignaturePacket.getSaltSize(hashAlgorithm); + int saltSize = in.read(); + if (saltSize != expectedSaltSize) + { + Streams.drain(in); + throw new UnsupportedPacketVersionException("Unexpected salt size " + expectedSaltSize + ", got " + saltSize); + } + + salt = new byte[saltSize]; + in.readFully(salt); + + keyFingerprint = new byte[32]; + in.readFully(keyFingerprint); + + isContaining = in.read(); + } + else + { + Streams.drain(in); + throw new UnsupportedPacketVersionException("Unsupported One-Pass-Signature packet version encountered: " + version); + } } - + + public static OnePassSignaturePacket createVersion3Packet( + int sigType, + int hashAlgorithm, + int keyAlgorithm, + long keyID, + boolean isNested) + { + return new OnePassSignaturePacket(sigType, hashAlgorithm, keyAlgorithm, keyID, isNested); + } + + public static OnePassSignaturePacket createVersion6Packet( + int sigType, + int hashAlgorithm, + int keyAlgorithm, + byte[] salt, + byte[] keyFingerprint, + boolean isNested) + { + return new OnePassSignaturePacket(sigType, hashAlgorithm, keyAlgorithm, salt, keyFingerprint, isNested); + } + + /** + * Create an OPS packet of version {@link #VERSION_3}. + * @param sigType signature type + * @param hashAlgorithm hash algorithm identifier + * @param keyAlgorithm public key algorithm identifier + * @param keyID key id + * @param isNested is nested flag + */ public OnePassSignaturePacket( int sigType, int hashAlgorithm, @@ -44,14 +113,55 @@ public OnePassSignaturePacket( long keyID, boolean isNested) { - this.version = 3; - this.sigType = sigType; + this(VERSION_3, sigType, hashAlgorithm, keyAlgorithm, null, keyID, null, isNested); + } + + /** + * Create an OPS packet of version {@link #VERSION_6}. + * + * @param sigType signature type + * @param hashAlgorithm hash algorithm identifier + * @param keyAlgorithm public key algorithm identifier + * @param salt salt + * @param keyFingerprint key fingerprint + * @param isNested is nested flag + */ + public OnePassSignaturePacket( + int sigType, + int hashAlgorithm, + int keyAlgorithm, + byte[] salt, + byte[] keyFingerprint, + boolean isNested) + { + this(VERSION_6, sigType, hashAlgorithm, keyAlgorithm, salt, 0, keyFingerprint, isNested); + } + + public OnePassSignaturePacket( + int version, + int signatureType, + int hashAlgorithm, + int keyAlgorithm, + byte[] salt, + long keyID, + byte[] keyFingerprint, + boolean isNested) + { + this.version = version; + this.sigType = signatureType; this.hashAlgorithm = hashAlgorithm; this.keyAlgorithm = keyAlgorithm; this.keyID = keyID; - this.isContaining = (isNested) ? 0 : 1; + this.keyFingerprint = keyFingerprint; + this.salt = salt; + this.isContaining = isNested ? 0 : 1; } - + + public int getVersion() + { + return version; + } + /** * Return the signature type. * @return the signature type @@ -76,8 +186,24 @@ public int getHashAlgorithm() { return hashAlgorithm; } + + /** + * Return the salt of the signature. + * Only for {@link #VERSION_6}, returns
null
otherwise. + * + * @return salt + */ + public byte[] getSalt() { + if (salt != null) { + return Arrays.clone(salt); + } + return null; + } /** + * Return the key-id of the issuing key. + * Only for {@link #VERSION_3}. Signatures of version {@link #VERSION_6} use {@link #getKeyFingerprint()} instead. + * * @return long */ public long getKeyID() @@ -85,6 +211,19 @@ public long getKeyID() return keyID; } + /** + * Return the v6 fingerprint of the issuing key. + * Only for {@link #VERSION_6}. Signatures of version {@link #VERSION_3} use {@link #getKeyID()} instead. + * + * @return 32 byte array + */ + public byte[] getKeyFingerprint() { + if (keyFingerprint != null) { + return Arrays.clone(keyFingerprint); + } + return null; + } + /** * Return true, if the signature contains any signatures that follow. * An bracketing OPS is followed by additional OPS packets and is calculated over all the data between itself @@ -112,14 +251,28 @@ public void encode( 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)); + if (version == VERSION_3) + { + 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)); + } + else if (version == VERSION_6) + { + pOut.write(salt.length); + pOut.write(salt); + + pOut.write(keyFingerprint); + } + else + { + throw new UnsupportedPacketVersionException("Unsupported One-Pass-Signature version encountered: " + version); + } pOut.write(isContaining); diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java index 5e3eac35b0..251f880c24 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/SignaturePacket.java @@ -13,9 +13,14 @@ /** * generic signature packet */ -public class SignaturePacket +public class SignaturePacket extends ContainedPacket implements PublicKeyAlgorithmTags { + public static final int VERSION_3 = 3; + public static final int VERSION_4 = 4; // https://datatracker.ietf.org/doc/rfc4880/ + public static final int VERSION_5 = 5; // https://datatracker.ietf.org/doc/draft-koch-openpgp-2015-rfc4880bis/ + public static final int VERSION_6 = 6; // https://datatracker.ietf.org/doc/draft-ietf-openpgp-crypto-refresh/ + private int version; private int signatureType; private long creationTime; @@ -27,216 +32,177 @@ public class SignaturePacket private SignatureSubpacket[] hashedData; private SignatureSubpacket[] unhashedData; private byte[] signatureEncoding; - - SignaturePacket( - BCPGInputStream in) - throws IOException + private byte[] salt = null; // v6 only + + public static SignaturePacket createVersion3Packet( + int signatureType, + long keyID, + int keyAlgorithm, + int hashAlgorithm, + long creationTime, + byte[] fingerPrint, + MPInteger[] signature) { - 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); + SignaturePacket signaturePacket = new SignaturePacket( + VERSION_3, + signatureType, + keyID, + keyAlgorithm, + hashAlgorithm, + null, + null, + fingerPrint, + null, + signature); - // - // read the signature sub packet data. - // - SignatureSubpacket sub; - SignatureSubpacketInputStream sIn = new SignatureSubpacketInputStream( - new ByteArrayInputStream(hashed)); + signaturePacket.creationTime = creationTime; - 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 - { - Streams.drain(in); + return signaturePacket; + } - throw new UnsupportedPacketVersionException("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: - case EDDSA_LEGACY: - 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; - signatureEncoding = Streams.readAll(in); - } - else - { - throw new IOException("unknown signature key algorithm: " + keyAlgorithm); - } - } + public static SignaturePacket createVersion4Packet( + int signatureType, + long keyID, + int keyAlgorithm, + int hashAlgorithm, + SignatureSubpacket[] hashedData, + SignatureSubpacket[] unhashedData, + byte[] fingerPrint, + MPInteger[] signature) + { + return new SignaturePacket( + VERSION_4, + signatureType, + keyID, + keyAlgorithm, + hashAlgorithm, + hashedData, + unhashedData, + fingerPrint, + null, + signature); } - + + public static SignaturePacket createVersion5Packet( + int signatureType, + long keyID, + int keyAlgorithm, + int hashAlgorithm, + SignatureSubpacket[] hashedData, + SignatureSubpacket[] unhashedData, + byte[] fingerPrint, + MPInteger[] signature) + { + return new SignaturePacket( + VERSION_5, + signatureType, + keyID, + keyAlgorithm, + hashAlgorithm, + hashedData, + unhashedData, + fingerPrint, + null, + signature); + } + + public static SignaturePacket createVersion6Packet( + int signatureType, + long keyID, + int keyAlgorithm, + int hashAlgorithm, + SignatureSubpacket[] hashedData, + SignatureSubpacket[] unhashedData, + byte[] fingerPrint, + byte[] salt, + MPInteger[] signature) + { + return new SignaturePacket( + VERSION_6, + signatureType, + keyID, + keyAlgorithm, + hashAlgorithm, + hashedData, + unhashedData, + fingerPrint, + salt, + signature); + } + /** * Generate a version 4 signature packet. - * - * @param signatureType - * @param keyAlgorithm - * @param hashAlgorithm - * @param hashedData - * @param unhashedData - * @param fingerPrint - * @param signature + * + * @param signatureType signature type + * @param keyAlgorithm signature algorithm + * @param hashAlgorithm hash algorithm + * @param hashedData hashed signature subpackets + * @param unhashedData unhashed signature subpackets + * @param fingerPrint signature fingerprint + * @param signature signature */ public SignaturePacket( - int signatureType, - long keyID, - int keyAlgorithm, - int hashAlgorithm, - SignatureSubpacket[] hashedData, - SignatureSubpacket[] unhashedData, - byte[] fingerPrint, - MPInteger[] signature) + 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); + this(VERSION_4, signatureType, keyID, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerPrint, null, signature); } - + /** * Generate a version 2/3 signature packet. - * - * @param signatureType - * @param keyAlgorithm - * @param hashAlgorithm - * @param fingerPrint - * @param signature + * + * @param version version + * @param signatureType signature type + * @param keyAlgorithm signature algorithm + * @param hashAlgorithm hash algorithm + * @param fingerPrint signature fingerprint + * @param signature signature */ public SignaturePacket( - int version, - int signatureType, - long keyID, - int keyAlgorithm, - int hashAlgorithm, - long creationTime, - byte[] fingerPrint, - MPInteger[] signature) + 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(version, signatureType, keyID, keyAlgorithm, hashAlgorithm, null, null, fingerPrint, null, signature); + this.creationTime = creationTime; } - + + /** + * Generate a signature packet. + * + * @param version signature version + * @param signatureType signature type + * @param keyID keyID of the signing key + * @param keyAlgorithm signature algorithm + * @param hashAlgorithm hash algorithm + * @param hashedData hashed signature subpackets + * @param unhashedData unhashed signature subpackets + * @param fingerPrint signature fingerprint + * @param signature signature + */ public SignaturePacket( - int version, - int signatureType, - long keyID, - int keyAlgorithm, - int hashAlgorithm, - SignatureSubpacket[] hashedData, - SignatureSubpacket[] unhashedData, - byte[] fingerPrint, - MPInteger[] signature) + int version, + int signatureType, + long keyID, + int keyAlgorithm, + int hashAlgorithm, + SignatureSubpacket[] hashedData, + SignatureSubpacket[] unhashedData, + byte[] fingerPrint, + byte[] salt, + MPInteger[] signature) { this.version = version; this.signatureType = signatureType; @@ -246,6 +212,7 @@ public SignaturePacket( this.hashedData = hashedData; this.unhashedData = unhashedData; this.fingerPrint = fingerPrint; + this.salt = salt; this.signature = signature; if (hashedData != null) @@ -253,7 +220,244 @@ public SignaturePacket( setCreationTime(); } } - + + SignaturePacket( + BCPGInputStream in) + throws IOException + { + version = in.read(); + + // TODO: Version 2 is not specified. Clarify? + if (version == VERSION_3 || version == 2) + { + parseVersion2or3Packet(in); + } + else if (version == VERSION_4 || version == VERSION_5) + { + parseVersion4or5Packet(in); + } + else if (version == VERSION_6) + { + parseVersion6Packet(in); + } + else + { + Streams.drain(in); + throw new UnsupportedPacketVersionException("unsupported version: " + version); + } + } + + private void parseVersion2or3Packet(BCPGInputStream in) throws IOException { + int l = in.read(); // MUST be 5. Enforce? + + signatureType = in.read(); + creationTime = (((long) in.read() << 24) | ((long) in.read() << 16) | ((long) 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(); + + // 2 byte fp + fingerPrint = readFingerprint(in); + + readSignature(in); + } + + private void parseVersion4or5Packet(BCPGInputStream in) throws IOException { + signatureType = in.read(); + keyAlgorithm = in.read(); + hashAlgorithm = in.read(); + + hashedData = readSubpackets(in, true); + unhashedData = readSubpackets(in, false); + + // 2 byte fp + fingerPrint = readFingerprint(in); + readSignature(in); + } + + private void parseVersion6Packet(BCPGInputStream in) throws IOException { + signatureType = in.read(); + keyAlgorithm = in.read(); + hashAlgorithm = in.read(); + + hashedData = readSubpackets(in, true); + unhashedData = readSubpackets(in, false); + + // 2 byte fp + fingerPrint = readFingerprint(in); + + readSalt(in); + + readSignature(in); + } + + private SignatureSubpacket[] readSubpackets(BCPGInputStream in, boolean isHashed) + throws IOException + { + int length = readLength(in); + byte[] data = new byte[length]; + + in.readFully(data); + + // + // read the signature sub packet data. + // + SignatureSubpacket sub; + SignatureSubpacketInputStream sIn = new SignatureSubpacketInputStream( + new ByteArrayInputStream(data)); + + Vector v = new Vector(); + while ((sub = sIn.readPacket()) != null) + { + v.addElement(sub); + } + + SignatureSubpacket[] subpackets = new SignatureSubpacket[v.size()]; + + for (int i = 0; i != subpackets.length; i++) + { + SignatureSubpacket p = v.elementAt(i); + if (isHashed) + { + if (p instanceof IssuerKeyID) + { + keyID = ((IssuerKeyID) p).getKeyID(); + } + else if (p instanceof SignatureCreationTime) + { + creationTime = ((SignatureCreationTime) p).getTime().getTime(); + } + } + else + { + if (p instanceof IssuerKeyID && keyID == 0) + { + keyID = ((IssuerKeyID) p).getKeyID(); + } + } + + subpackets[i] = p; + } + + return subpackets; + } + + private byte[] readFingerprint(BCPGInputStream in) throws IOException { + byte[] fingerPrint = new byte[2]; + in.readFully(fingerPrint); + return fingerPrint; + } + + private void readSalt(BCPGInputStream in) throws IOException { + int saltSize = in.read(); + + int expectedSaltSize = getSaltSize(hashAlgorithm); + if (expectedSaltSize != -1 && saltSize != getSaltSize(hashAlgorithm)) { + throw new IOException("Salt length mismatch. Expected " + expectedSaltSize + " bytes, but signature indicates " + saltSize); + } + + salt = new byte[saltSize]; + in.readFully(salt); + } + + private void readSignature(BCPGInputStream in) throws IOException { + 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: + case EDDSA_LEGACY: + MPInteger ecR = new MPInteger(in); + MPInteger ecS = new MPInteger(in); + + signature = new MPInteger[2]; + signature[0] = ecR; + signature[1] = ecS; + break; + case Ed25519: + signature = null; + signatureEncoding = new byte[64]; + in.readFully(signatureEncoding); + break; + case Ed448: + signature = null; + signatureEncoding = new byte[114]; + in.readFully(signatureEncoding); + break; + default: + if (keyAlgorithm >= PublicKeyAlgorithmTags.EXPERIMENTAL_1 && keyAlgorithm <= PublicKeyAlgorithmTags.EXPERIMENTAL_11) + { + signature = null; + signatureEncoding = Streams.readAll(in); + } + else + { + throw new IOException("unknown signature key algorithm: " + keyAlgorithm); + } + } + } + + private int readLength(BCPGInputStream in) throws IOException { + int hashedLength; + if (version == VERSION_4) + { + hashedLength = (in.read() << 8) | in.read(); + } + else + { + hashedLength = (in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read(); + } + return hashedLength; + } + + static int getSaltSize(int hashAlgorithm) + { + switch (hashAlgorithm) { + case HashAlgorithmTags.SHA256: + case HashAlgorithmTags.SHA224: + case HashAlgorithmTags.SHA3_256: + return 16; + case HashAlgorithmTags.SHA384: + return 24; + case HashAlgorithmTags.SHA512: + case HashAlgorithmTags.SHA3_512: + return 32; + default: + return -1; + } + } + /** * get the version number */ @@ -298,7 +502,7 @@ public byte[] getSignatureTrailer() { byte[] trailer = null; - if (version == 3 || version == 2) + if (version == VERSION_3 || version == 2) { trailer = new byte[5]; @@ -330,7 +534,12 @@ public byte[] getSignatureTrailer() } byte[] data = hOut.toByteArray(); - + + if (version == VERSION_6) + { + sOut.write((byte)(data.length >> 24)); + sOut.write((byte)(data.length >> 16)); + } sOut.write((byte)(data.length >> 8)); sOut.write((byte)data.length); sOut.write(data); @@ -439,7 +648,7 @@ public void encode( pOut.write(version); - if (version == 3 || version == 2) + if (version == VERSION_3 || version == 2) { pOut.write(5); // the length of the next block @@ -463,7 +672,7 @@ public void encode( pOut.write(keyAlgorithm); pOut.write(hashAlgorithm); } - else if (version == 4) + else if (version == VERSION_4 || version == VERSION_5 || version == VERSION_6) { pOut.write(signatureType); pOut.write(keyAlgorithm); @@ -477,7 +686,12 @@ else if (version == 4) } byte[] data = sOut.toByteArray(); - + + if (version == VERSION_6) + { + pOut.write(data.length >> 24); + pOut.write(data.length >> 16); + } pOut.write(data.length >> 8); pOut.write(data.length); pOut.write(data); @@ -490,7 +704,12 @@ else if (version == 4) } data = sOut.toByteArray(); - + + if (version == VERSION_6) + { + pOut.write(data.length >> 24); + pOut.write(data.length >> 16); + } pOut.write(data.length >> 8); pOut.write(data.length); pOut.write(data); @@ -502,6 +721,12 @@ else if (version == 4) pOut.write(fingerPrint); + if (version == VERSION_6) + { + pOut.write(salt.length); + pOut.write(salt); + } + if (signature != null) { for (int i = 0; i != signature.length; i++) @@ -519,6 +744,10 @@ else if (version == 4) out.writePacket(SIGNATURE, bOut.toByteArray()); } + public byte[] getSalt() { + return Arrays.clone(salt); + } + private void setCreationTime() { for (int i = 0; i != hashedData.length; i++) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java index e4b2985ad9..c709c5bfe6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPOnePassSignature.java @@ -183,11 +183,21 @@ public boolean verify( return verifier.verify(pgpSig.getSignature()); } + public int getVersion() + { + return sigPack.getVersion(); + } + public long getKeyID() { return sigPack.getKeyID(); } + public byte[] getKeyFingerprint() + { + return sigPack.getKeyFingerprint(); + } + public int getSignatureType() { return sigPack.getSignatureType(); @@ -203,6 +213,11 @@ public int getKeyAlgorithm() return sigPack.getKeyAlgorithm(); } + public byte[] getSalt() + { + return sigPack.getSalt(); + } + /** * Return true, if the signature is contains any signatures that follow. * An bracketing OPS is followed by additional OPS packets and is calculated over all the data between itself diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java index cbe645acdd..0243beea70 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java @@ -18,6 +18,7 @@ import org.bouncycastle.bcpg.SignaturePacket; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.TrustPacket; +import org.bouncycastle.bcpg.UnsupportedPacketVersionException; import org.bouncycastle.bcpg.UserAttributeSubpacket; import org.bouncycastle.openpgp.operator.PGPContentVerifier; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilder; @@ -127,6 +128,17 @@ public int getHashAlgorithm() return sigPck.getHashAlgorithm(); } + /** + * Return the salt associated with this signature. + * Only for {@link SignaturePacket#VERSION_6} signatures. + * + * @return salt + */ + public byte[] getSalt() + { + return sigPck.getSalt(); + } + /** * Return the digest prefix of the signature. * @@ -703,7 +715,12 @@ public static boolean isSignatureEncodingEqual(PGPSignature sig1, PGPSignature s public static PGPSignature join(PGPSignature sig1, PGPSignature sig2) throws PGPException { - if (!isSignatureEncodingEqual(sig1, sig2)) + if (sig1.getVersion() < SignaturePacket.VERSION_4) + { + throw new UnsupportedPacketVersionException("Cannot merge signatures with versions other than 4, 5, 6."); + } + + if (sig1.getVersion() != sig2.getVersion() || !isSignatureEncodingEqual(sig1, sig2)) { throw new IllegalArgumentException("These are different signatures."); } @@ -734,17 +751,19 @@ public static PGPSignature join(PGPSignature sig1, PGPSignature sig2) } SignatureSubpacket[] unhashed = (SignatureSubpacket[])merged.toArray(new SignatureSubpacket[0]); + return new PGPSignature( - new SignaturePacket( - sig1.getSignatureType(), - sig1.getKeyID(), - sig1.getKeyAlgorithm(), - sig1.getHashAlgorithm(), - sig1.getHashedSubPackets().packets, - unhashed, - sig1.getDigestPrefix(), - sig1.sigPck.getSignature() - ) + new SignaturePacket( + sig1.getVersion(), + sig1.getSignatureType(), + sig1.getKeyID(), + sig1.getKeyAlgorithm(), + sig1.getHashAlgorithm(), + sig1.sigPck.getHashedSubPackets(), + unhashed, + sig1.getDigestPrefix(), + sig1.sigPck.getSalt(), + sig1.sigPck.getSignature()) ); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java index a4ea7cf11d..88de6a8b03 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java @@ -184,7 +184,7 @@ public PGPOnePassSignature generateOnePassVersion( boolean isNested) throws PGPException { - return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested)); + return new PGPOnePassSignature(OnePassSignaturePacket.createVersion3Packet(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested)); } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java index 605e6608ba..46b50fad72 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPV3SignatureGenerator.java @@ -151,7 +151,7 @@ public PGPOnePassSignature generateOnePassVersion( boolean isNested) throws PGPException { - return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested)); + return new PGPOnePassSignature(OnePassSignaturePacket.createVersion3Packet(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested)); } /** diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java index 5c8023cbbb..f4670f98d3 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.security.NoSuchProviderException; import java.security.Security; import java.security.SignatureException; @@ -14,7 +15,9 @@ import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.OnePassSignaturePacket; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.SignaturePacket; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketInputStream; import org.bouncycastle.bcpg.SignatureSubpacketTags; @@ -762,6 +765,9 @@ public void performTest() testSignatureTarget(); testUserAttributeEncoding(); testExportNonExportableSignature(); + + // v6 Signature + testParseV6Signature(); } private void testUserAttributeEncoding() @@ -1367,6 +1373,50 @@ public void testExportNonExportableSignature() isTrue(nonExportableSig.getEncoded(true).length == 0); } + private void testParseV6Signature() throws IOException { + // Test vector from https://openpgp-wg.gitlab.io/rfc4880bis/#name-sample-inline-signed-messag + String inlineSignedMessage = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "xEYGAQobIHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usyxhsTwYJppfk\n" + + "1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkBy0p1AAAAAABXaGF0IHdlIG5lZWQgZnJv\n" + + "bSB0aGUgZ3JvY2VyeSBzdG9yZToKCi0gdG9mdQotIHZlZ2V0YWJsZXMKLSBub29k\n" + + "bGVzCsKYBgEbCgAAACkFgmOYo2MiIQbLGGxPBgmml+TVLfpscisMHx4nwYpWcI9l\n" + + "JewnutmsyQAAAABpNiB2SV9QIYiQ9/Xi7jwYIlFPcFAPVR2G5ckh5ATjSlP7rCfQ\n" + + "b7gKqPxbyxbhljGygHQPnqau1eBzrQD5QVplPEDnemrnfmkrpx0GmhCfokxYz9jj\n" + + "FtCgazStmsuOXF9SFQE=\n" + + "-----END PGP MESSAGE-----"; + byte[] salt = Hex.decode("76495f50218890f7f5e2ee3c1822514f70500f551d86e5c921e404e34a53fbac"); + byte[] fingerprint = Hex.decode("cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc9"); + + ByteArrayInputStream bIn = new ByteArrayInputStream(inlineSignedMessage.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + PGPObjectFactory pgpFactory = new BcPGPObjectFactory(aIn); + + PGPOnePassSignatureList opsList = (PGPOnePassSignatureList) pgpFactory.nextObject(); + isEquals(1, opsList.size()); + PGPOnePassSignature ops = opsList.get(0); + isEquals(OnePassSignaturePacket.VERSION_6, ops.getVersion()); + isEquals(PGPSignature.CANONICAL_TEXT_DOCUMENT, ops.getSignatureType()); + isEquals(PublicKeyAlgorithmTags.Ed25519, ops.getKeyAlgorithm()); + isEquals(HashAlgorithmTags.SHA512, ops.getHashAlgorithm()); + isTrue(Arrays.areEqual(salt, ops.getSalt())); + isTrue(Arrays.areEqual(fingerprint, ops.getKeyFingerprint())); + isTrue(ops.isContaining()); + + PGPLiteralData literalData = (PGPLiteralData) pgpFactory.nextObject(); + + PGPSignatureList sigList = (PGPSignatureList) pgpFactory.nextObject(); + isEquals(1, sigList.size()); + PGPSignature signature = sigList.get(0); + isEquals(SignaturePacket.VERSION_6, signature.getVersion()); + isEquals(PGPSignature.CANONICAL_TEXT_DOCUMENT, signature.getSignatureType()); + isEquals(PublicKeyAlgorithmTags.Ed25519, signature.getKeyAlgorithm()); + isEquals(HashAlgorithmTags.SHA512, signature.getHashAlgorithm()); + IssuerFingerprint issuerFp = signature.getHashedSubPackets().getIssuerFingerprint(); + isTrue(Arrays.areEqual(fingerprint, issuerFp.getFingerprint())); + isTrue(Arrays.areEqual(salt, signature.getSalt())); + } + private PGPSignatureList readSignatures(String armored) throws IOException {