def create_signed_resource(options, manifestInput): # Create the Resource structure first, and encode it using specified encoding scheme # This encoded data will then be used for signing try: resource = Resource(resource=get_manifest(options, manifestInput), resourceType=Resource.TYPE_MANIFEST) except errorhandler.InvalidObject as err: LOG.critical('Unable to create resource object: {0}'.format(err)) sys.exit(1) # Convert the Python object to a Python dictionary, and then encode to # specified encoding format resource_encoded = utils.encode(resource.to_dict(), options, manifest_definition.Resource()) signature = None crypto_mode = get_crypto_mode(options, manifestInput) if isPsk(crypto_mode): signature = get_symmetric_signature(options, manifestInput, enc_data=resource_encoded) else: signature = get_signature(options, manifestInput, enc_data=resource_encoded) try: sresource = SignedResource(resource=resource, signature=signature) return sresource.to_dict() except errorhandler.InvalidObject as err: LOG.critical('Unable to create signed resource: {0}'.format(err)) sys.exit(1)
def main(options): LOG.debug('Creating new manifest from input file and options') # Parse input files and options. Generate hydrated and hierachial manifest JSON. manifest = create_signed_resource(options) LOG.debug('Manifest python object successfully created from ASN.1 definition and input') # Encode data if requested output = utils.encode(manifest, options, manifest_definition.SignedResource()) LOG.debug('Manifest successfully encoded into desired format ({}). Size: {} bytes.'.format(options.encoding, len(output))) # And we're done write_result(output, options)
def create_signed_resource(options): # Read options from manifest input file/ # if (options.input_file.isatty()): # LOG.info("Reading data from from active TTY... Terminate input with ^D") manifestInput = {'applyImmediately': True} try: if os.path.exists(defaults.config): with open(defaults.config) as f: manifestInput.update(json.load(f)) if not options.input_file.isatty(): content = options.input_file.read() if content and len( content) >= 2: #The minimum size of a JSON file is 2: '{}' manifestInput.update(json.loads(content)) except ValueError as e: LOG.critical("JSON Decode Error: {}".format(e)) sys.exit(1) # Create the Resource structure first, and encode it using specified encoding scheme # This encoded data will then be used for signing try: resource = Resource(resource=get_manifest(options, manifestInput), resourceType=Resource.TYPE_MANIFEST) except errorhandler.InvalidObject as err: LOG.critical('Unable to create resource object: {0}'.format(err)) sys.exit(1) # Convert the Python object to a Python dictionary, and then encode to # specified encoding format resource_encoded = utils.encode(resource.to_dict(), options, manifest_definition.Resource()) signature = None crypto_mode = get_crypto_mode(options, manifestInput) if isPsk(crypto_mode): signature = get_symmetric_signature(options, manifestInput, enc_data=resource_encoded) else: signature = get_signature(options, manifestInput, enc_data=resource_encoded) try: sresource = SignedResource(resource=resource, signature=signature) return sresource.to_dict() except errorhandler.InvalidObject as err: LOG.critical('Unable to create signed resource: {0}'.format(err)) sys.exit(1)
def sign(options): # Load defaults defaultConfig = {} if os.path.exists(defaults.config): with open(defaults.config) as f: defaultConfig = json.load(f) # Load the existing manifest content = None if hasattr(options, 'manifest') and options.manifest: options.manifest.seek(0) content = options.manifest.read() else: content = utils.download_file(options.url) # Extract the signed resource. signed_resource_data = { "der": lambda d: codec.bin2obj(d, definition.SignedResource(), der_decoder), }[options.encoding](content) if not signed_resource_data: return 1 LOG.debug('Decoded SignedResource from {} encoded binary'.format( options.encoding)) # Sign the resource # Calculate the hash of the content of the existing manifest c_hash = utils.sha_hash(signed_resource_data['resource']) if not 'hash' in signed_resource_data['signature']: LOG.critical('Manifest does not contain a hash') return None # Extract the hash e_hash = binascii.a2b_hex(signed_resource_data['signature']['hash']) # Compare calculated and extracted hashes if c_hash != e_hash: LOG.critical('Hash mismatch\nExpected: {}\nActual: {}'.format( binascii.b2a_hex(e_hash), binascii.b2a_hex(c_hash))) return None LOG.debug('Manifest hash: {}'.format(binascii.b2a_hex(c_hash))) # Load a certificate and private key # Private key must be in .manifest_tool.json, or provided on the command-line # 1. Check the command-line if not hasattr(options, 'private_key') or not options.private_key: # 2. Check the default config if 'private-key' in defaultConfig: try: # NOTE: binary is not specified since the key is usually PEM encoded. options.private_key = open(defaultConfig['private-key'], 'r') except: LOG.critical( 'No private key specified and default key ({}) cannot be opened' .format(defaultConfig['private-key'])) return 1 # 3. Fail if the private key is not found if not hasattr(options, 'private_key') or not options.private_key: LOG.critical( 'No private key specified and default key ({}) cannot be opened'. format(defaultConfig['private-key'])) return 1 if not hasattr(options, 'password'): options.password = None # Load the private key privkey = load_pem_private_key(options.private_key.read(), password=options.password, backend=default_backend()) # Make sure that this is an ECDSA key! if not isinstance(privkey, ec.EllipticCurvePrivateKey): LOG.critical('Private key was not an ECC private key') return 1 LOG.debug('Loaded private key') LOG.info('Signing manifest...') # Create signature sig = privkey.sign(signed_resource_data['resource'], ec.ECDSA(hashes.SHA256())) LOG.debug('Signature: {}'.format(binascii.b2a_hex(sig))) # destroy the privkey object privkey = None # Certificate must be in .manifest_tool.json, or provided on the command-line # Load the certificate if not hasattr(options, 'certificate') or not options.certificate: if 'default-certificates' in defaultConfig: options.certificate = open( defaultConfig['default-certificates'][0]['file'], 'rb') if not hasattr(options, 'certificate') or not options.certificate: LOG.critical( 'No certificate specified and default certificate ({}) cannot be opened' .format(defaultConfig['default-certificates'][0]['file'])) return 1 # Load the certificate object from the DER file certObj = None try: certObj = x509.load_der_x509_certificate(options.certificate.read(), default_backend()) except ValueError as e: LOG.critical("X.509 Certificate Error in ({file}): {error}".format( error=e, file=options.certificate.name)) return (1) if not certObj: LOG.critical("({file}) is not a valid certificate".format(file=cPath)) return (1) # Make sure that the certificate is signed with SHA256 if not isinstance(certObj.signature_hash_algorithm, hashes.SHA256): LOG.critical( "In ({file}): Only SHA256 certificates are supported by the update client at this time." .format(file=cPath)) return (1) LOG.info('Verifying signature with supplied certificate...') # Verify the signature with the provided certificate to ensure that the update target will be able to do so try: pubkey = certObj.public_key() pubkey.verify(sig, signed_resource_data['resource'], ec.ECDSA(hashes.SHA256())) except InvalidSignature as e: LOG.critical( 'New signature failed to verify with supplied certificate ({})'. format(options.certificate.name)) return 1 # Store the fingerprint of the certificate fingerprint = certObj.fingerprint(hashes.SHA256()) certificates = [] # for idx in range(len()) # certObj = None # try: # certObj = x509.load_der_x509_certificate(options.certificate.read(), default_backend()) # except ValueError as e: # LOG.critical("X.509 Certificate Error in ({file}): {error}".format(error=e, file=options.certificate.name)) # return(1) # # if not certObj: # LOG.critical("({file}) is not a valid certificate".format(file=cPath)) # return(1) # if not isinstance(certObj.signature_hash_algorithm, hashes.SHA256): # LOG.critical("In ({file}): Only SHA256 certificates are supported by the update client at this time.".format(file=cPath)) # return(1) # LOG.debug('Creating certificate reference ({}) from {} with fingerprint {}'.format(idx, options.certificate.name, fingerprint)) LOG.debug( 'Creating certificate reference from {} with fingerprint {}'.format( options.certificate.name, fingerprint)) uri = '' # TODO: Insert URI for delegation of trust cr = schema.CertificateReference(fingerprint=fingerprint, uri=uri) # Append the certificate reference to the current list # NOTE: Currently, only one certificate reference will exist in the certificates list, but with delegation of trust # there will be more certificate references certificates.append(cr) LOG.debug( 'Signed hash ({}) of encoded content ({} bytes) with resulting signature {}' .format(binascii.b2a_hex(c_hash), len(content), binascii.b2a_hex(sig))) signatures = [] for s in signed_resource_data['signature']['signatures']: signatures.append(sig_from_dict(s)) # encode the signature block signatures.append( schema.SignatureBlock(signature=sig, certificates=certificates)) # encode the resource signature resource_signature = schema.ResourceSignature(hash=c_hash, signatures=signatures) # encode the signed resource signed_resource = schema.SignedResource( resource=signed_resource_data['resource'], signature=resource_signature) # Convert the signed resource into a python dictionary manifest_dict = signed_resource.to_dict() # Encode the Python dictionary as a DER stream output = utils.encode(manifest_dict, options, definition.SignedResource()) # Write the result to the output_file if hasattr(options, 'output_file') and options.output_file: # Write result to output file or stdout buffer. options.output_file.write(output) # Append newline if outputting to TTY if options.output_file.isatty(): options.output_file.write(b'\n') return 0 return 1