blob: c3afb85c8a84dabc27a2ee460de9e7f61b4f46ba [file] [log] [blame]
#!/usr/bin/env python2
#
# Copyright 2016 Dirkjan Ochtman.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
'''
Script to generate *ring* test file for RSA PKCS1 v1.5 signing test vectors
from the NIST FIPS 186-4 test vectors. Takes as single argument on the
command-line the path to the test vector file (tested with SigGen15_186-3.txt).
Requires the cryptography library from pyca.
'''
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
import hashlib
import sys, copy
DIGEST_OUTPUT_LENGTHS = {
'SHA1': 80,
'SHA256': 128,
'SHA384': 192,
'SHA512': 256
}
# Prints reasons for skipping tests
DEBUG = False
def debug(str, flag):
if flag:
sys.stderr.write(str + "\n")
sys.stderr.flush()
# Some fields in the input files are encoded without a leading "0", but
# `x.decode('hex')` requires every byte to be encoded with two hex digits.
def from_hex(hex):
return (hex if len(hex) % 2 == 0 else "0" + hex).decode('hex')
def to_hex(bytes):
return ''.join('{:02x}'.format(ord(b)) for b in bytes)
# Some fields in the input files are encoded without a leading "0", but the
# *ring* test framework requires every byte to be encoded with two hex digits.
def reformat_hex(hex):
return to_hex(from_hex(hex))
def parse(fn, last_field):
'''Parse input test vector file, leaving out comments and empty lines, and
returns a list of self-contained test cases. Depends on the last_field
being the last value in each test case.'''
cases = []
with open(fn) as f:
cur = {}
for ln in f:
if not ln.strip():
continue
if ln[0] in {'#', '['}:
continue
name, val = ln.split('=', 1)
cur[name.strip()] = val.strip()
if name.strip() == last_field:
cases.append(cur)
cur = copy.copy(cur)
return cases
def print_sign_test(case, n, e, d, padding_alg):
# Recover the prime factors and CRT numbers.
p, q = rsa.rsa_recover_prime_factors(n, e, d)
# cryptography returns p, q with p < q by default. *ring* requires
# p > q, so swap them here.
p, q = max(p, q), min(p, q)
dmp1 = rsa.rsa_crt_dmp1(d, p)
dmq1 = rsa.rsa_crt_dmq1(d, q)
iqmp = rsa.rsa_crt_iqmp(p, q)
# Create a private key instance.
pub = rsa.RSAPublicNumbers(e, n)
priv = rsa.RSAPrivateNumbers(p, q, d, dmp1, dmq1, iqmp, pub)
key = priv.private_key(default_backend())
msg = case['Msg'].decode('hex')
# Recalculate and compare the signature to validate our processing.
if padding_alg == 'PKCS#1 1.5':
sig = key.sign(msg, padding.PKCS1v15(),
getattr(hashes, case['SHAAlg'])())
hex_sig = to_hex(sig)
assert hex_sig == case['S']
elif padding_alg == "PSS":
# PSS is randomised, can't recompute this way
pass
else:
print "Invalid padding algorithm"
quit()
# Serialize the private key in DER format.
der = key.private_bytes(serialization.Encoding.DER,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption())
# Print the test case data in the format used by *ring* test files.
print 'Digest = %s' % case['SHAAlg']
print 'Key = %s' % to_hex(der)
print 'Msg = %s' % reformat_hex(case['Msg'])
if padding_alg == "PSS":
print 'Salt = %s' % reformat_hex(case['SaltVal'])
print 'Sig = %s' % reformat_hex(case['S'])
print 'Result = Pass'
print ''
def print_verify_test(case, n, e):
# Create a private key instance.
pub = rsa.RSAPublicNumbers(e, n)
key = pub.public_key(default_backend())
der = key.public_bytes(serialization.Encoding.DER,
serialization.PublicFormat.PKCS1)
# Print the test case data in the format used by *ring* test files.
print 'Digest = %s' % case['SHAAlg']
print 'Key = %s' % to_hex(der)
print 'Msg = %s' % reformat_hex(case['Msg'])
print 'Sig = %s' % reformat_hex(case['S'])
print 'Result = %s' % case['Result']
print ''
def main(fn, test_type, padding_alg):
input_file_digest = hashlib.sha384(open(fn, 'rb').read()).hexdigest()
# File header
print "# RSA %(padding_alg)s Test Vectors for FIPS 186-4 from %(fn)s in" % \
{ "fn": fn, "padding_alg": padding_alg }
print "# http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-3rsatestvectors.zip"
print "# accessible from"
print "# http://csrc.nist.gov/groups/STM/cavp/digital-signatures.html#test-vectors"
print "# with SHA-384 digest %s" % (input_file_digest)
print "# filtered and reformatted using util/generate-rsa-signing-tests.py."
print "#"
print "# Digest = SHAAlg."
if test_type == "verify":
print "# Key is (n, e) encoded in an ASN.1 (DER) sequence."
elif test_type == "sign":
print "# Key is an ASN.1 (DER) RSAPrivateKey."
else:
print "Invalid test_type: %s" % test_type
quit()
print "# Sig = S."
print
num_cases = 0
# Each test type has a different field as the last entry per case
# For verify tests,PKCS "Result" is always the last field.
# Otherwise, for signing tests, it is dependent on the padding used.
if test_type == "verify":
last_field = "Result"
else:
if padding_alg == "PSS":
last_field = "SaltVal"
else:
last_field = "S"
for case in parse(fn, last_field):
if case['SHAAlg'] == 'SHA224':
# SHA224 not supported in *ring*.
debug("Skipping due to use of SHA224", DEBUG)
continue
if padding_alg == "PSS":
if case['SHAAlg'] == 'SHA1':
# SHA-1 with PSS not supported in *ring*.
debug("Skipping due to use of SHA1 and PSS.", DEBUG)
continue
# *ring* only supports PSS where the salt length is equal to the
# output length of the hash algorithm.
if len(case['SaltVal']) * 2 != DIGEST_OUTPUT_LENGTHS[case['SHAAlg']]:
debug("Skipping due to unsupported salt length.", DEBUG)
continue
# Read private key components.
n = int(case['n'], 16)
e = int(case['e'], 16)
d = int(case['d'], 16)
if n.bit_length() // 8 < 2048 // 8:
debug("Skipping due to modulus length (too small).", DEBUG)
continue
if test_type == 'sign':
if n.bit_length() > 4096:
debug("Skipping due to modulus length (too large).", DEBUG)
continue
print_sign_test(case, n, e, d, padding_alg)
else:
print_verify_test(case, n, e)
num_cases += 1
debug("%d test cases output." % num_cases, True)
if __name__ == '__main__':
if len(sys.argv) != 2:
print "Usage:\n python %s <filename>" % sys.argv[0]
else:
fn = sys.argv[1]
if 'PSS' in fn:
pad_alg = 'PSS'
elif '15' in fn:
pad_alg = 'PKCS#1 1.5'
else:
print "Could not determine padding algorithm,"
quit()
if 'Gen' in fn:
test_type = 'sign'
elif 'Ver' in fn:
test_type = 'verify'
else:
print "Could not determine test type."
quit()
main(sys.argv[1], test_type, pad_alg)