Esempio n. 1
0
async def sign_authenticode_file(context, orig_path, fmt, *, authenticode_comment=None):
    """Sign a file in-place with authenticode, using autograph as a backend.

    Args:
        context (Context): the signing context
        orig_path (str): the source file to sign
        fmt (str): the format to sign with
        comment (str): The authenticode comment to sign with, if present.
                       currently only used for msi files.
                       (Defaults to None)

    Returns:
        True on success, False otherwise

    """
    if winsign.osslsigncode.is_signed(orig_path):
        log.info("%s is already signed", orig_path)
        return True

    fmt, keyid = utils.split_autograph_format(fmt)

    async def signer(digest, digest_algo):
        try:
            return await sign_hash_with_autograph(context, digest, fmt, keyid)
        except Exception:
            log.exception("Error signing authenticode hash with autograph")
            raise

    infile = orig_path
    outfile = orig_path + "-new"
    if "authenticode_ev" in fmt:
        digest_algo = "sha256"
    else:
        digest_algo = "sha1"

    if keyid:
        certs = load_pem_certs(open(context.config[f"authenticode_cert_{keyid}"], "rb").read())
    else:
        certs = load_pem_certs(open(context.config["authenticode_cert"], "rb").read())

    url = context.config["authenticode_url"]
    timestamp_style = context.config["authenticode_timestamp_style"]
    if fmt.endswith("authenticode_stub"):
        crosscert = context.config["authenticode_cross_cert"]
    else:
        crosscert = None

    if authenticode_comment and orig_path.endswith(".msi"):
        log.info("Using comment '%s' to sign %s", authenticode_comment, orig_path)
    elif authenticode_comment:
        log.info("Not using specified comment to sign %s, not yet implemented for non *.msi files.", orig_path)
        authenticode_comment = None

    if not await winsign.sign.sign_file(
        infile, outfile, digest_algo, certs, signer, url=url, comment=authenticode_comment, crosscert=crosscert, timestamp_style=timestamp_style,
    ):
        raise IOError(f"Couldn't sign {orig_path}")
    os.rename(outfile, infile)

    return True
Esempio n. 2
0
def test_timestamp_old(test_file, digest_algo, tmp_path, signing_keys,
                       httpserver):
    """Verify that we can sign with old style timestamps."""
    signed_exe = tmp_path / "signed.exe"

    priv_key = load_private_key(open(signing_keys[0], "rb").read())
    certs = load_pem_certs(signing_keys[1].read_bytes())

    def signer(digest, digest_algo):
        return sign_signer_digest(priv_key, digest_algo, digest)

    httpserver.serve_content(
        (DATA_DIR / f"unsigned-{digest_algo}-ts-old.dat").read_bytes())
    assert sign_file(
        test_file,
        signed_exe,
        digest_algo,
        certs,
        signer,
        timestamp_style="old",
        # Comment this out to use a real timestamp server so that we can
        # capture a response
        timestamp_url=httpserver.url,
    )

    # Check that we have 3 certificates in the signature
    if is_pefile(test_file):
        with signed_exe.open("rb") as f:
            certificates = get_certificates(f)
            sigs = get_signatures_from_certificates(certificates)
            assert len(certificates) == 1
            assert len(sigs) == 1
            assert len(sigs[0]["certificates"]) == 3

            assert verify_pefile(f)
Esempio n. 3
0
def test_sign_file_dummy(tmp_path, signing_keys):
    """Check that we can sign with an additional dummy certificate.

    The extra dummy certs are used by the stub installer.
    """
    test_file = DATA_DIR / "unsigned.exe"
    signed_exe = tmp_path / "signed.exe"

    priv_key = load_private_key(open(signing_keys[0], "rb").read())
    certs = load_pem_certs(signing_keys[1].read_bytes())

    def signer(digest, digest_algo):
        return sign_signer_digest(priv_key, digest_algo, digest)

    assert sign_file(test_file,
                     signed_exe,
                     "sha1",
                     certs,
                     signer,
                     crosscert=signing_keys[1])

    # Check that we have 2 certificates in the signature
    with signed_exe.open("rb") as f:
        certificates = get_certificates(f)
        sigs = get_signatures_from_certificates(certificates)
        assert len(certificates) == 1
        assert len(sigs) == 1
        assert len(sigs[0]["certificates"]) == 2
Esempio n. 4
0
async def async_main(argv=None):
    """Main CLI entry point for signing (async)."""
    parser = build_parser()
    args = parser.parse_args(argv)

    logging.basicConfig(format="%(asctime)s - %(message)s",
                        level=args.loglevel)

    if not args.priv_key:
        if not (args.autograph_user and args.autograph_secret):
            parser.error(
                "--key, or all of --autograph-url, --autograph-user, and "
                "--autograph-secret must be specified")

    if not args.outfile:
        args.outfile = args.infile

    certs = []
    certs_data = open(args.certs, "rb").read()
    certs = load_pem_certs(certs_data)
    priv_key = load_private_key(open(args.priv_key, "rb").read())

    signer = key_signer(priv_key)

    with tempfile.TemporaryDirectory() as d:
        d = Path(d)
        if args.infile == "-":
            args.infile = d / "unsigned"
            with args.infile.open("wb") as f:
                _copy_stream(sys.stdin.buffer, f)
        else:
            args.infile = Path(args.infile)

        if args.outfile == "-":
            outfile = d / "signed"
        else:
            outfile = Path(args.outfile)

        r = await sign_file(
            args.infile,
            outfile,
            args.digest_algo,
            certs,
            signer,
            url=args.url,
            comment=args.comment,
            timestamp_style=args.timestamp,
        )

        # TODO: Extra cross-cert
        # TODO: Check with a cert chain
        if not r:
            return 1

        if args.outfile == "-":
            with outfile.open("rb") as f:
                _copy_stream(f, sys.stdout.buffer)

        return 0
Esempio n. 5
0
def test_sign_file_badfile(tmp_path, signing_keys):
    """Verify that we can't sign non-exe files."""
    test_file = Path(__file__)
    signed_file = tmp_path / "signed.py"

    priv_key = load_private_key(open(signing_keys[0], "rb").read())
    certs = load_pem_certs(signing_keys[1].read_bytes())

    def signer(digest, digest_algo):
        return sign_signer_digest(priv_key, digest_algo, digest)

    assert not sign_file(test_file, signed_file, "sha1", certs, signer)
Esempio n. 6
0
async def test_sign(test_file, digest_algo, tmp_path, signing_keys):
    """Check that we can sign a PE file."""
    signed_exe = tmp_path / "signed.exe"

    priv_key = load_private_key(open(signing_keys[0], "rb").read())
    certs = load_pem_certs(signing_keys[1].read_bytes())
    # TODO: Make sure multiple works
    cert = certs[0]

    async def signer(digest, digest_algo):
        return sign_signer_digest(priv_key, digest_algo, digest)

    with test_file.open("rb") as infile, signed_exe.open("wb+") as outfile:
        assert await sign_file(infile, outfile, digest_algo, cert, signer)

        assert verify_pefile(outfile)
Esempio n. 7
0
def test_sign_file_twocerts(tmp_path, signing_keys):
    """Check that we can include multiple certificates."""
    test_file = DATA_DIR / "unsigned.exe"
    signed_exe = tmp_path / "signed.exe"

    priv_key = load_private_key(open(signing_keys[0], "rb").read())
    certs = load_pem_certs(open(DATA_DIR / "twocerts.pem", "rb").read())

    def signer(digest, digest_algo):
        return sign_signer_digest(priv_key, digest_algo, digest)

    assert sign_file(test_file, signed_exe, "sha1", certs, signer)

    # Check that we have 2 certificates in the signature
    with signed_exe.open("rb") as f:
        certificates = get_certificates(f)
        sigs = get_signatures_from_certificates(certificates)
        assert len(certificates) == 1
        assert len(sigs) == 1
        assert len(sigs[0]["certificates"]) == 2
Esempio n. 8
0
def test_timestamp_rfc3161(test_file, digest_algo, tmp_path, signing_keys,
                           httpserver):
    """Verify that we can sign with RFC3161 timestamps."""
    signed_exe = tmp_path / "signed.exe"

    priv_key = load_private_key(open(signing_keys[0], "rb").read())
    certs = load_pem_certs(signing_keys[1].read_bytes())

    def signer(digest, digest_algo):
        return sign_signer_digest(priv_key, digest_algo, digest)

    httpserver.serve_content(
        (DATA_DIR / f"unsigned-{digest_algo}-ts-rfc3161.dat").read_bytes())
    assert sign_file(
        test_file,
        signed_exe,
        digest_algo,
        certs,
        signer,
        timestamp_style="rfc3161",
        # Comment this out to use a real timestamp server so that we can
        # capture a response
        timestamp_url=httpserver.url,
    )

    # Check that we have 1 certificate in the signature,
    # and have a counterSignature section
    if is_pefile(test_file):
        with signed_exe.open("rb") as f:
            certificates = get_certificates(f)
            sigs = get_signatures_from_certificates(certificates)
            assert len(certificates) == 1
            assert len(sigs) == 1
            assert len(sigs[0]["certificates"]) == 1
            assert any((sigs[0]["signerInfos"][0]["unauthenticatedAttributes"]
                        [i]["type"] == id_timestampSignature) for i in range(
                            len(sigs[0]["signerInfos"][0]
                                ["unauthenticatedAttributes"])))

            assert verify_pefile(f)
Esempio n. 9
0
async def sign_authenticode_file(context, orig_path, fmt):
    """Sign a file in-place with authenticode, using autograph as a backend.

    Args:
        context (Context): the signing context
        orig_path (str): the source file to sign
        fmt (str): the format to sign with

    Returns:
        True on success, False otherwise

    """
    if winsign.osslsigncode.is_signed(orig_path):
        log.info("%s is already signed", orig_path)
        return True

    async def signer(digest, digest_algo):
        try:
            return await sign_hash_with_autograph(context, digest, fmt)
        except Exception:
            log.exception("Error signing authenticode hash with autograph")
            raise

    infile = orig_path
    outfile = orig_path + "-new"
    digest_algo = "sha1"
    certs = load_pem_certs(open(context.config["authenticode_cert"], "rb").read())
    url = context.config["authenticode_url"]
    timestamp_style = context.config["authenticode_timestamp_style"]
    if fmt.endswith("authenticode_stub"):
        crosscert = context.config["authenticode_cross_cert"]
    else:
        crosscert = None

    if not await winsign.sign.sign_file(infile, outfile, digest_algo, certs, signer, url=url, crosscert=crosscert, timestamp_style=timestamp_style):
        raise IOError(f"Couldn't sign {orig_path}")
    os.rename(outfile, infile)

    return True
Esempio n. 10
0
def test_sign_file(test_file, digest_algo, tmp_path, signing_keys):
    """Check that we can sign with the osslsign wrapper."""
    signed_exe = tmp_path / "signed.exe"

    priv_key = load_private_key(open(signing_keys[0], "rb").read())
    certs = load_pem_certs(signing_keys[1].read_bytes())

    def signer(digest, digest_algo):
        return sign_signer_digest(priv_key, digest_algo, digest)

    assert sign_file(test_file, signed_exe, digest_algo, certs, signer)

    # Check that we have 1 certificate in the signature
    if test_file in TEST_PE_FILES:
        assert is_pefile(test_file)
        with signed_exe.open("rb") as f:
            certificates = get_certificates(f)
            sigs = get_signatures_from_certificates(certificates)
            assert len(certificates) == 1
            assert len(sigs) == 1
            assert len(sigs[0]["certificates"]) == 1

            assert verify_pefile(f)
Esempio n. 11
0
async def sign_file(
    infile,
    outfile,
    digest_algo,
    certs,
    signer,
    url=None,
    comment=None,
    crosscert=None,
    timestamp_style=None,
    timestamp_url=None,
):
    """Sign a PE or MSI file.

    Args:
        infile (str): Path to the unsigned file
        outfile (str): Path to where the signed file will be written
        digest_algo (str): Which digest algorithm to use. Generally 'sha1' or 'sha256'
        certs (str): Path to where the PEM encoded public certificate(s) are located
        signer (function): Function that takes (digest, digest_algo) and
                           returns bytes of the signature. Normally this will
                           be using a private key object to sign the digest.
        url (str): A URL to embed into the signature
        comment (str): A string to embed into the signature
        crosscert (str): Extra certificates to attach to the signature
        timestamp_style (str): What kind of signed timestamp to include in the
                               signature. Can be None, 'old', or 'rfc3161'.
        timestamp_url (str): URL for the timestamp server to use. Required if
                             timestamp_style is set.


    Returns:
        True on success
        False otherwise

    """
    infile = Path(infile)
    outfile = Path(outfile)
    try:
        log.debug("Generating dummy signature")
        old_sig = get_dummy_signature(
            infile, digest_algo, url=url, comment=comment, crosscert=crosscert
        )
    except OSError:
        log.error("Couldn't generate dummy signature")
        log.debug("Exception:", exc_info=True)
        return False

    try:
        log.debug("Re-signing with real keys")
        old_sig = get_signeddata(old_sig)
        if crosscert:
            crosscert = Path(crosscert)
            certs.extend(load_pem_certs(crosscert.read_bytes()))
        newsig = await resign(old_sig, certs, signer)
    except Exception:
        log.error("Couldn't re-sign")
        log.debug("Exception:", exc_info=True)
        return False

    if timestamp_style == "old":
        ci = der_decode(newsig, ContentInfo())[0]
        sig = der_decode(ci["content"], SignedData())[0]
        sig = await winsign.timestamp.add_old_timestamp(sig, timestamp_url)
        ci = ContentInfo()
        ci["contentType"] = id_signedData
        ci["content"] = sig
        newsig = der_encode(ci)
    elif timestamp_style == "rfc3161":
        ci = der_decode(newsig, ContentInfo())[0]
        sig = der_decode(ci["content"], SignedData())[0]
        sig = await winsign.timestamp.add_rfc3161_timestamp(
            sig, digest_algo, timestamp_url
        )
        ci = ContentInfo()
        ci["contentType"] = id_signedData
        ci["content"] = sig
        newsig = der_encode(ci)

    try:
        log.debug("Attaching new signature")
        write_signature(infile, outfile, newsig)
    except Exception:
        log.error("Couldn't write new signature")
        log.debug("Exception:", exc_info=True)
        return False

    log.debug("Done!")
    return True