diff --git a/cmake/JSSTests.cmake b/cmake/JSSTests.cmake index 375fcf329..7427308dc 100644 --- a/cmake/JSSTests.cmake +++ b/cmake/JSSTests.cmake @@ -217,6 +217,22 @@ macro(jss_tests) DEPENDS "Setup_DBs" ) endif() + jss_test_exec( + NAME "Setup_Bouncy_Castle_Jar" + COMMAND "wget" "https://www.bouncycastle.org/download/bcprov-jdk15on-167.jar" "-P" "/tmp/" + ) + jss_test_exec( + NAME "Compile_RSAOAEPSHA2_with_BC_classpath" + COMMAND "javac" "-classpath" "/tmp/bcprov-jdk15on-167.jar:./" "${JSS_TEST_DIR}/VerifyRSAOAEPSHA2.java" + DEPENDS "Setup_Bouncy_Castle_Jar" + ) + if(HAVE_NSS_OAEP) + jss_test_java( + NAME "JSS-OAEP-Vector-Test" + COMMAND "org.mozilla.jss.tests.VerifyRSAOAEPSHA2" + DEPENDS "Setup_Bouncy_Castle_Jar" + ) + endif() jss_test_java( NAME "Mozilla_JSS_JCA_Signature" COMMAND "org.mozilla.jss.tests.JCASigTest" "${RESULTS_NSSDB_OUTPUT_DIR}" "${PASSWORD_FILE}" diff --git a/org/mozilla/jss/tests/VerifyRSAOAEPSHA2.java b/org/mozilla/jss/tests/VerifyRSAOAEPSHA2.java new file mode 100644 index 000000000..e1bfd3d1e --- /dev/null +++ b/org/mozilla/jss/tests/VerifyRSAOAEPSHA2.java @@ -0,0 +1,416 @@ +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.RSAPrivateKeySpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import javax.xml.bind.DatatypeConverter; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +class TestVectorData { + public BigInteger pub_key_modulus; + public BigInteger pub_key_exponent; + public BigInteger priv_key_public_exponent; + public BigInteger priv_key_modulus; + public BigInteger priv_key_exponent; + public BigInteger priv_key_prime_1; + public BigInteger priv_key_prime_2; + public BigInteger priv_key_prime_exponent_1; + public BigInteger priv_key_prime_exponent_2; + public BigInteger priv_key_coefficient; + public byte[] plaintext; + public byte[] ciphertext; +} + +class TestVectorLoader { + private static final String FILE_HEADER = "# RSA OAEP SHA2 vectors built"; + private static final String EXAMPLE_HEADER = "# ====="; + private static final String EXAMPLE = "# Example"; + private static final String PUBLIC_KEY = "# Public key"; + private static final String PUB_MODULUS = "# Modulus:"; + private static final String PUB_EXPONENT = "# Exponent:"; + private static final String PRIVATE_KEY = "# Private key"; + private static final String PRIV_MODULUS = "# Modulus:"; + private static final String PRIV_PUBLIC_EXPONENT = "# Public exponent:"; + private static final String PRIV_EXPONENT = "# Exponent:"; + private static final String PRIV_PRIME_1 = "# Prime 1:"; + private static final String PRIV_PRIME_2 = "# Prime 2:"; + private static final String PRIV_PRIME_EXPONENT_1 = "# Prime exponent 1:"; + private static final String PRIV_PRIME_EXPONENT_2 = "# Prime exponent 2:"; + private static final String PRIV_COEFFICIENT = "# Coefficient:"; + private static final String OAEP_EXAMPLE_HEADER = "# OAEP Example"; + private static final String MESSAGE = "# Message:"; + private static final String ENCRYPTION = "# Encryption:"; + + private BufferedReader m_reader = null; + private FileReader m_file_reader = null; + private TestVectorData m_data = null; + + TestVectorLoader() { + + } + + protected void finalize() { + close(); + } + + public void open(String path) throws IOException { + close(); + m_file_reader = new FileReader(path); + m_reader = new BufferedReader(m_file_reader); + m_data = new TestVectorData(); + } + + public void close() { + try { + if (m_reader != null) { + m_reader.close(); + m_reader = null; + } + if (m_file_reader != null) { + m_file_reader.close(); + m_file_reader = null; + } + m_data = null; + } catch (IOException e) { + System.out.println("Exception closing files"); + e.printStackTrace(); + } + } + + public TestVectorData loadNextTest() throws IOException { + if (m_file_reader == null || m_reader == null || m_data == null) { + throw new IOException("A test vector file must be opened first"); + } + + String line = m_reader.readLine(); + + if (line == null) { + // end of file + return null; + } + + if (line.startsWith(FILE_HEADER)) { + // start of file + skipFileHeader(m_reader); + line = m_reader.readLine(); + } + + if (line.startsWith(OAEP_EXAMPLE_HEADER)) { + // Next example, keep existing keys and load next message + loadMessage(m_reader, m_data); + return m_data; + } + + // otherwise it's a new example + if (!line.startsWith(EXAMPLE_HEADER)) { + throw new IOException("Test Header Missing"); + } + startNewTest(m_reader); + m_data = new TestVectorData(); + + line = m_reader.readLine(); + if (!line.startsWith(PUBLIC_KEY)) + throw new IOException("Public Key Missing"); + loadPublicKey(m_reader, m_data); + + line = m_reader.readLine(); + if (!line.startsWith(PRIVATE_KEY)) + throw new IOException("Private Key Missing"); + loadPrivateKey(m_reader, m_data); + + line = m_reader.readLine(); + if (!line.startsWith(OAEP_EXAMPLE_HEADER)) + throw new IOException("Message Missing"); + loadMessage(m_reader, m_data); + + return m_data; + } + + private byte[] unhexlify(String line) { + byte[] bytes = DatatypeConverter.parseHexBinary(line); + return bytes; + } + + private BigInteger readBigInteger(BufferedReader br) throws IOException { + return new BigInteger(br.readLine(), 16); + } + + private void skipFileHeader(BufferedReader br) throws IOException { + br.readLine(); // # # Derived from the NIST OAEP SHA1 vectors + br.readLine(); // # # Verified against the Bouncy Castle OAEP SHA2 implementation + br.readLine(); // # + } + + private void startNewTest(BufferedReader br) throws IOException { + String line = br.readLine(); + if (!line.startsWith(EXAMPLE)) + throw new IOException("Example Header Missing"); + } + + private void loadPublicKey(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(PUB_MODULUS)) + throw new IOException("Public Key Modulus Missing"); + data.pub_key_modulus = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PUB_EXPONENT)) + throw new IOException("Public Key Exponent Missing"); + data.pub_key_exponent = readBigInteger(br); + } + + private void loadPrivateKey(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(PRIV_MODULUS)) + throw new IOException("Private Key Modulus Missing"); + data.priv_key_modulus = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PUBLIC_EXPONENT)) + throw new IOException("Private Key Public Exponent Missing"); + data.priv_key_public_exponent = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_EXPONENT)) + throw new IOException("Private Key Exponent Missing"); + data.priv_key_exponent = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_1)) + throw new IOException("Private Key Prime 1 Missing"); + data.priv_key_prime_1 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_2)) + throw new IOException("Private Key Prime 2 Missing"); + data.priv_key_prime_2 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_EXPONENT_1)) + throw new IOException("Private Key Prime Exponent 1 Missing"); + data.priv_key_prime_exponent_1 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_EXPONENT_2)) + throw new IOException("Private Key Prime Exponent 2 Missing"); + data.priv_key_prime_exponent_2 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_COEFFICIENT)) + throw new IOException("Private Key Coefficient Missing"); + data.priv_key_coefficient = readBigInteger(br); + } + + private void loadMessage(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(MESSAGE)) + throw new IOException("Plaintext Missing"); + data.plaintext = unhexlify(br.readLine()); + + line = br.readLine(); + if (!line.startsWith(ENCRYPTION)) + throw new IOException("Ciphertext Missing"); + data.ciphertext = unhexlify(br.readLine()); + } + +} + +public class VerifyRSAOAEPSHA2 { + + public enum SHAHash { + SHA1, SHA224, SHA256, SHA384, SHA512 + } + + private SHAHash m_mgf1_hash; + private SHAHash m_alg_hash; + private Cipher m_cipher; + private PrivateKey m_private_key; + private AlgorithmParameters m_algo_param; + + VerifyRSAOAEPSHA2(SHAHash mgf1_hash, SHAHash alg_hash, TestVectorData test_data) throws Exception { + + m_mgf1_hash = mgf1_hash; + m_alg_hash = alg_hash; + + MGF1ParameterSpec mgf1_spec = getMGF1ParameterSpec(m_mgf1_hash); + AlgorithmParameterSpec algo_param_spec = getAlgorithmParameterSpec(m_alg_hash, mgf1_spec); + + m_algo_param = AlgorithmParameters.getInstance("OAEP"); + m_algo_param.init(algo_param_spec); + + m_private_key = loadPrivateKey(test_data); + + m_cipher = getCipher(m_alg_hash); + } + + private Cipher getCipher(SHAHash alg_hash) throws GeneralSecurityException { + Cipher cipher = null; + + switch (alg_hash) { + + case SHA1: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA1andMGF1Padding", "BC"); + break; + + case SHA224: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-224andMGF1Padding", "BC"); + break; + + case SHA256: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding", "BC"); + break; + + case SHA384: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-384andMGF1Padding", "BC"); + break; + + case SHA512: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-512andMGF1Padding", "BC"); + break; + } + + return cipher; + } + + private MGF1ParameterSpec getMGF1ParameterSpec(SHAHash mgf1_hash) { + MGF1ParameterSpec mgf1 = null; + + switch (mgf1_hash) { + + case SHA1: + mgf1 = MGF1ParameterSpec.SHA1; + break; + case SHA224: + mgf1 = MGF1ParameterSpec.SHA224; + break; + + case SHA256: + mgf1 = MGF1ParameterSpec.SHA256; + break; + + case SHA384: + mgf1 = MGF1ParameterSpec.SHA384; + break; + + case SHA512: + mgf1 = MGF1ParameterSpec.SHA512; + break; + } + + return mgf1; + } + + private AlgorithmParameterSpec getAlgorithmParameterSpec(SHAHash alg_hash, MGF1ParameterSpec mgf1_spec) { + + OAEPParameterSpec oaep_spec = null; + + switch (alg_hash) { + + case SHA1: + oaep_spec = new OAEPParameterSpec("SHA1", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA224: + oaep_spec = new OAEPParameterSpec("SHA-224", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA256: + oaep_spec = new OAEPParameterSpec("SHA-256", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA384: + oaep_spec = new OAEPParameterSpec("SHA-384", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA512: + oaep_spec = new OAEPParameterSpec("SHA-512", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + } + + return oaep_spec; + } + + private PrivateKey loadPrivateKey(TestVectorData test_data) throws Exception { + KeyFactory kf = KeyFactory.getInstance("RSA"); + + RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(test_data.priv_key_modulus, test_data.priv_key_exponent); + + return kf.generatePrivate(keySpec); + } + + public void testDecrypt(byte[] plaintext, byte[] ciphertext) throws Exception { + System.out.println("Verifying OAEP with mgf1_hash: " + m_mgf1_hash + " alg_hash: " + m_alg_hash + " - " + + ciphertext.length + " bytes ciphertext - " + + plaintext.length + " bytes plaintext"); + + m_cipher.init(Cipher.DECRYPT_MODE, m_private_key, m_algo_param); + byte[] java_plaintext = m_cipher.doFinal(ciphertext); + + if (Arrays.equals(java_plaintext, plaintext) == false) { + throw new Exception("Verification failure - plaintext does not match after decryption."); + } + } + + public static void main(String[] args) { + Security.addProvider(new BouncyCastleProvider()); + + // assume current directory if no path given on command line + String vector_path = "./vectors/cryptography_vectors/asymmetric/RSA/oaep-custom"; + + if (args.length > 0) { + vector_path = args[0]; + } + + System.out.println("Vector file path: " + vector_path); + + try { + // loop over each combination of hash loading the vector file + // to verify for each + for (SHAHash mgf1_hash : SHAHash.values()) { + for (SHAHash alg_hash : SHAHash.values()) { + if (mgf1_hash.name().toLowerCase().equals("sha1") && + alg_hash.name().toLowerCase().equals("sha1")) { + continue; + } + String filename = "oaep-" + mgf1_hash.name().toLowerCase() + + "-" + alg_hash.name().toLowerCase() + ".txt"; + + System.out.println("Loading " + filename + "..."); + + TestVectorLoader loader = new TestVectorLoader(); + loader.open(vector_path + filename); + + TestVectorData test_data; + + // load each test in the file and verify + while ((test_data = loader.loadNextTest()) != null) { + VerifyRSAOAEPSHA2 verify = new VerifyRSAOAEPSHA2(mgf1_hash, alg_hash, test_data); + verify.testDecrypt(test_data.plaintext, test_data.ciphertext); + } + + System.out.println("Verifying " + filename + " completed successfully."); + } + } + + System.out.println("All verification completed successfully"); + + } catch (Exception e) { + // if any exception is thrown the verification has failed + e.printStackTrace(); + System.out.println("Verification Failed!"); + } + } +}