Example #1
0
def get_signature(options, manifestInput, enc_data):
    signatures = manifestGet(manifestInput, 'signature.signatures') or []
    input_hash = manifestGet(manifestInput, 'signature.hash') or b''
    signing_tool = manifestGet(manifestInput, 'signing-tool') or ''
    if getattr(options, 'signing_tool', None):
        signing_tool = options.signing_tool

    # There should always be a signing key or signing tool on create.
    if not signing_tool and not getattr(options, 'private_key', None):
        if 'private-key' in manifestInput:
            try:
                options.private_key = open(manifestInput['private-key'], 'r')
            except:
                LOG.critical(
                    'No private key specified and default key ({}) cannot be opened'
                    .format(manifestInput['private-key']))
                sys.exit(1)
        else:
            LOG.critical(
                'Resource is not signed and no signing key is provided.')
            sys.exit(1)

    # Get SHA-256 hash of content and sign it using private key
    sha_content = utils.sha_hash(enc_data)
    if len(signatures):
        # If a signature is provided in the input json, then the encoded content must match the provided hash
        # Signature validation is not performed, since this would require certificate acquisition, which may not be
        # possible
        if sha_content != binascii.a2b_hex(input_hash):
            LOG.critical(
                'Manifest hash provided in input file does not match hashed output'
            )
            LOG.critical('Expected: {0}'.format(input_hash))
            LOG.critical('Actual:   {0}'.format(binascii.b2a_hex(sha_content)))
            sys.exit(1)
        # TODO: perform best-effort signature validation

    signature = None
    if signing_tool:
        # get the key id
        key_id = manifestGet(manifestInput, 'signing-key-id')
        if hasattr(options, 'signing_key_id') and options.signing_key_id:
            key_id = options.signing_key_id
        digest_algo = 'sha256'
        infile = None
        with tempfile.NamedTemporaryFile(delete=False) as f:
            infile = f.name
            f.write(enc_data)
            f.flush()
            LOG.debug('Temporary manifest file: {}'.format(infile))
        outfile = None
        with tempfile.NamedTemporaryFile(delete=False) as f:
            outfile = f.name
            LOG.debug('Temporary signature file: {}'.format(outfile))

        try:
            cmd = [signing_tool, digest_algo, key_id, infile, outfile]
            LOG.debug('Running "{}" to sign manifest.'.format(' '.join(cmd)))
            # This command line is constructed internally, so we ignore bandit
            # warnings about executing a Popen. See:
            # https://bandit.readthedocs.io/en/latest/plugins/b603_subprocess_without_shell_equals_true.html
            p = subprocess.Popen(cmd)  #nosec
            p.wait()
            if p.returncode != 0:
                LOG.critical('Signing tool failed.')
                sys.exit(1)
            with open(outfile, 'rb') as f:
                signature = f.read()

        except:
            LOG.critical('Failed to execute signing tool.')
            sys.exit(1)
        finally:
            os.unlink(infile)
            os.unlink(outfile)
        LOG.debug('Signature: {}'.format(
            binascii.b2a_hex(signature).decode('utf-8')))

    elif hasattr(options, 'private_key') and options.private_key:
        sk = ecdsa.SigningKey.from_pem(options.private_key.read())
        signature = sk.sign_digest(sha_content,
                                   sigencode=ecdsa.util.sigencode_der)

    certificates = []

    # pick a signature block with no signature in it.
    inputCerts = manifestGet(manifestInput, 'certificates') or []

    # If no certificate was provided in the manifest input or in options,
    if len(inputCerts) == 0:
        # then load the default certificate
        inputCerts = manifestInput.get('default-certificates', [])

    # If there is still no certificate,
    if len(inputCerts) == 0:
        # Search through all signature blocks for one that contains certificates but no signature
        for idx, sb in enumerate(signatures):
            if not 'signature' in sb:
                inputCerts = sb.get('certificates', [])
                # This signature will be appended later so we must trim it.
                del signatures[idx]
                break

    for idx, cert in enumerate(inputCerts):
        if not any(k in cert for k in ('file', 'uri')):
            LOG.critical(
                'Could not find "file" or "uri" property for certificate')
            sys.exit(1)

        # If 'file', we just use the content in local file
        if 'file' in cert:
            fPath = cert['file']
            if not os.path.isabs(fPath):
                fPath = os.path.join(os.path.dirname(options.input_file.name),
                                     cert['file'])
            content = utils.read_file(fPath)

        # Else we download the file contents
        else:
            content = utils.download_file(cert['uri'])
        # Figure our which extension the certificate has
        contentPath = cert['file'] if 'file' in cert else cert['uri']
        ext = contentPath.rsplit('.', 1)[1]

        # Read the certificate file, and get DER encoded data
        if ext == 'pem':
            lines = content.replace(" ", '').split()
            content = binascii.a2b_base64(''.join(lines[1:-1]))

        # Verify the certificate hash algorithm
        # Extract subjectPublicKeyInfo field from X.509 certificate (see RFC3280)
        # fingerprint = utils.sha_hash(content)
        cPath = cert['file'] if 'file' in cert else cert['uri']
        certObj = None
        try:
            certObj = x509.load_der_x509_certificate(
                content, cryptoBackends.default_backend())
        except ValueError as e:
            LOG.critical("X.509 Certificate Error in ({file}): {error}".format(
                error=e, file=cPath))
            sys.exit(1)

        if not certObj:
            LOG.critical(
                "({file}) is not a valid certificate".format(file=cPath))
            sys.exit(1)
        if not isinstance(certObj.signature_hash_algorithm,
                          cryptoHashes.SHA256):
            LOG.critical(
                "In ({file}): Only SHA256 certificates are supported by the Device Management Update client at this time."
                .format(file=cPath))
            sys.exit(1)
        fingerprint = certObj.fingerprint(cryptoHashes.SHA256())

        LOG.debug(
            'Creating certificate reference ({}) from {} with fingerprint {}'.
            format(idx, contentPath, fingerprint))
        uri = ''
        if 'uri' in cert:
            uri = cert['uri']
        certificates.append(
            CertificateReference(fingerprint=fingerprint, uri=uri))

        LOG.debug(
            'Signed hash ({}) of encoded content ({} bytes) with resulting signature {}'
            .format(sha_content, len(enc_data), signature))
    if signature:
        signatures.append(
            SignatureBlock(signature=signature, certificates=certificates))
    return ResourceSignature(hash=sha_content, signatures=signatures)
Example #2
0
def get_signature(options, manifestInput, enc_data):
    signatures = manifestGet(manifestInput,'signature.signatures') or []
    input_hash = manifestGet(manifestInput,'signature.hash') or b''

    # There should always be a signing key on create.
    if not hasattr(options,'private_key') or not options.private_key:
        if 'private-key' in manifestInput:
            try:
                options.private_key = open(manifestInput['private-key'],'r')
            except:
                LOG.critical('No private key specified and default key ({}) cannot be opened'.format(manifestInput['private-key']))
                sys.exit(1)
        else:
            LOG.critical('Resource is not signed and no signing key is provided.')
            sys.exit(1)

    # Get SHA-256 hash of content and sign it using private key
    sha_content = utils.sha_hash(enc_data)
    if len(signatures):
        # If a signature is provided in the input json, then the encoded content must match the provided hash
        # Signature validation is not performed, since this would require certificate acquisition, which may not be
        # possible
        if sha_content != binascii.a2b_hex(input_hash):
            LOG.critical('Manifest hash provided in input file does not match hashed output')
            LOG.critical('Expected: {0}'.format(input_hash))
            LOG.critical('Actual:   {0}'.format(binascii.b2a_hex(sha_content)))
            sys.exit(1)
        # TODO: perform best-effort signature validation

    if hasattr(options, 'private_key') and options.private_key:
        sk = ecdsa.SigningKey.from_pem(options.private_key.read())
        sig = sk.sign_digest(sha_content, sigencode=ecdsa.util.sigencode_der)

        certificates = []

        # pick a signature block with no signature in it.
        inputCerts = manifestGet(manifestInput, 'certificates') or []

        # If no certificate was provided in the manifest input or in options,
        if len(inputCerts) == 0:
            # then load the default certificate
            inputCerts = manifestInput.get('default-certificates', [])

        # If there is still no certificate,
        if len(inputCerts) == 0:
            # Search through all signature blocks for one that contains certificates but no signature
            for idx, sb in enumerate(signatures):
                 if not 'signature' in sb:
                     inputCerts = sb.get('certificates', [])
                     # This signature will be appended later so we must trim it.
                     del signatures[idx]
                     break

        for idx, cert in enumerate(inputCerts):
            if not any(k in cert for k in ('file', 'uri')):
                LOG.critical('Could not find "file" or "uri" property for certificate')
                sys.exit(1)

            # If 'file', we just use the content in local file
            if 'file' in cert:
                fPath = cert['file']
                if not os.path.isabs(fPath):
                    fPath = os.path.join(os.path.dirname(options.input_file.name), cert['file'])
                content = utils.read_file(fPath)

            # Else we download the file contents
            else:
                content = utils.download_file(cert['uri'])
            # Figure our which extension the certificate has
            contentPath = cert['file'] if 'file' in cert else cert['uri']
            ext = contentPath.rsplit('.', 1)[1]

            # Read the certificate file, and get DER encoded data
            if ext == 'pem':
                lines = content.replace(" ",'').split()
                content = binascii.a2b_base64(''.join(lines[1:-1]))

            # Verify the certificate hash algorithm
            # Extract subjectPublicKeyInfo field from X.509 certificate (see RFC3280)
            # fingerprint = utils.sha_hash(content)
            cPath = cert['file'] if 'file' in cert else cert['uri']
            certObj = None
            try:
                certObj = x509.load_der_x509_certificate(content, cryptoBackends.default_backend())
            except ValueError as e:
                LOG.critical("X.509 Certificate Error in ({file}): {error}".format(error=e, file=cPath))
                sys.exit(1)

            if not certObj:
                LOG.critical("({file}) is not a valid certificate".format(file=cPath))
                sys.exit(1)
            if not isinstance(certObj.signature_hash_algorithm, cryptoHashes.SHA256):
                LOG.critical("In ({file}): Only SHA256 certificates are supported by the mbed Cloud Update client at this time.".format(file=cPath))
                sys.exit(1)
            fingerprint = certObj.fingerprint(cryptoHashes.SHA256())

            LOG.debug('Creating certificate reference ({}) from {} with fingerprint {}'.format(idx, contentPath, fingerprint))
            uri = ''
            if 'uri' in cert:
                uri = cert['uri']
            certificates.append(CertificateReference(
                fingerprint = fingerprint,
                uri = uri
            ))

        LOG.debug('Signed hash ({}) of encoded content ({} bytes) with resulting signature {}'.format(
            sha_content, len(enc_data), sig))
        signatures.append(SignatureBlock(signature = sig, certificates = certificates))
    return ResourceSignature(
            hash = sha_content,
            signatures = signatures
        )
Example #3
0
def get_symmetric_signature(options, manifestInput, enc_data):
    input_hash = manifestGet(manifestInput, 'signature.hash') or b''

    # There should always be a signing key on create.
    if not hasattr(options, 'private_key') or not options.private_key:
        if 'psk-master-key' in manifestInput:
            try:
                options.private_key = open(manifestInput['psk-master-key'],
                                           'rb')
            except:
                LOG.critical(
                    'No PSK master key specified and default key ({}) cannot be opened'
                    .format(manifestInput['private-key']))
                sys.exit(1)
        else:
            LOG.critical(
                'Resource is not signed and no PSK master key is provided.')
            sys.exit(1)

    if not symmetricArgsOkay(options):
        LOG.critical('--mac requires:')
        LOG.critical(
            '    --private-key: The master key for generating device PSKs')
        #                                                                                 80 chars ->|
        #             ================================================================================
        LOG.critical(
            '    --psk-table: the output file for PSKs. This argument is optional/ignored'
        )
        LOG.critical('                 when inline encoding is used.')
        LOG.critical(
            '    --psk-table-encoding: the encoding to use for the PSK output file.'
        )
        LOG.critical('    Either:')
        LOG.critical(
            '        --device-urn: The device URN for the target device (Endpoint Client Name)'
        )
        LOG.critical('    OR:')
        LOG.critical(
            '        --filter-id: The filter identifier used to gather device URNs'
        )
        sys.exit(1)

    # Get SHA-256 hash of content and sign it using private key
    sha_content = utils.sha_hash(enc_data)
    # If a hash is provided in the input json, then the encoded content must match the provided hash
    if input_hash and sha_content != binascii.a2b_hex(input_hash):
        LOG.critical(
            'Manifest hash provided in input file does not match hashed output'
        )
        LOG.critical('Expected: {0}'.format(input_hash))
        LOG.critical('Actual:   {0}'.format(binascii.b2a_hex(sha_content)))
        sys.exit(1)

    # Load a default URN out of the settings file.
    devices = manifestGet(manifestInput, 'deviceURNs') or []
    LOG.info('Loaded device URNs from {!r}'.format(defaults.config))
    for dev in devices:
        LOG.info('    {!r}'.format(dev))

    # Optionally load a URN out of the command-line arguments
    if hasattr(options, 'device_urn') and options.device_urn:
        cmdDevs = [options.device_urn]
        LOG.info('Loaded device URNs from input arguments')
        for dev in cmdDevs:
            LOG.info('    {!r}'.format(dev))
        devices.extend(cmdDevs)
    if hasattr(options, 'filter_id') and options.filter_id:
        LOG.critical('Device Filters not supported yet.')
        sys.exit(1)

    # Use only unique devices
    devices = list(set(devices))

    crypto_mode = get_crypto_mode(options, manifestInput)

    payload_key = b''
    if hasattr(options, 'payload_key') and options.payload_key:
        LOG.debug('Converting payload key ({0}) to binary'.format(
            options.payload_key))
        payload_key = bytes(binascii.a2b_hex(options.payload_key))
    LOG.debug('Payload key length: {0}'.format(len(payload_key)))
    master_key = options.private_key.read()
    vendor_id = manifestGet(manifestInput,
                            'resource.resource.manifest.vendorId', 'vendorId')
    class_id = manifestGet(manifestInput, 'resource.resource.manifest.classId',
                           'classId')
    iv = os.urandom(13)
    deviceSymmetricInfos = {}
    # For each device
    maxIndexSize = 0
    maxRecordSize = 0
    LOG.info('Creating per-device validation codes...')
    for device in devices:
        LOG.info('    {!r}'.format(device))

        hkdf = utils.getDevicePSK_HKDF(
            cryptoMode(crypto_mode).name, master_key,
            uuid.UUID(vendor_id).bytes,
            uuid.UUID(class_id).bytes, b'Authentication')
        psk = hkdf.derive(bytes(device, 'utf-8'))
        maxIndexSize = max(maxIndexSize, len(device))
        # Now encrypt the hash with the selected AE algorithm.
        pskCipherData = getDevicePSKData(crypto_mode, psk, iv, sha_content,
                                         payload_key)
        recordData = der_encoder.encode(
            OctetString(
                hexValue=binascii.b2a_hex(pskCipherData).decode("ascii")))
        maxRecordSize = max(maxRecordSize, len(recordData))
        deviceSymmetricInfos[device] = recordData
    # print (deviceSymmetricInfos)
    def proto_encode(x):
        keytable = keytable_pb2.KeyTable()

        for k, d in x.items():
            entry = keytable.entries.add()
            entry.urn = k
            entry.opaque = d
        return keytable.SerializeToString()

    # Save the symmetric info file
    encodedSymmertricInfos = {
        'json':
        lambda x: json.JSONEncoder(default=binascii.b2a_base64).encode(x),
        'cbor':
        lambda x: None,
        'protobuf':
        proto_encode,
        'text':
        lambda x: '\n'.join([
            ','.join([binascii.b2a_hex(y) for y in (k, d)])
            for k, d in x.items()
        ]) + '\n'
        # 'inline' : lambda x : None
    }.get(options.psk_table_encoding)(deviceSymmetricInfos)
    options.psk_table.write(encodedSymmertricInfos)

    #=========================
    # PSK ID is the subject key identifier (hash) of the master key.
    # The URI of the "certificate" is the location at which to find the key table or key query.
    # PSK is known only to the signer and the device
    # PSK Signature is AE(PSK, hash), but it is not included, since it must be distributed in the key table.
    shaMaster = cryptoHashes.Hash(cryptoHashes.SHA256(),
                                  cryptoBackends.default_backend())
    shaMaster.update(master_key)
    subjectKeyIdentifier = shaMaster.finalize()

    mb = MacBlock(pskID=subjectKeyIdentifier,
                  keyTableIV=iv,
                  keyTableRef='thismessage://1',
                  keyTableVersion=0,
                  keyTableRecordSize=maxRecordSize,
                  keyTableIndexSize=maxIndexSize)
    macs = [mb]
    rs = ResourceSignature(hash=sha_content, signatures=[], macs=macs)
    return rs