You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/src/apps/misc/sign_identity.py

186 lines
5.5 KiB

from ustruct import pack, unpack
from trezor import ui, wire
from trezor.crypto.hashlib import sha256
from trezor.messages.SignedIdentity import SignedIdentity
from trezor.ui.components.tt.text import Text
from trezor.utils import chunks
from apps.common import HARDENED, coininfo
from apps.common.confirm import require_confirm
from apps.common.keychain import get_keychain
from apps.common.paths import AlwaysMatchingSchema
if False:
from typing import List, Optional, Union
from trezor.messages.IdentityType import IdentityType
from trezor.messages.SignIdentity import SignIdentity
from trezor.ui.components.common.text import TextContent
from apps.common.paths import Bip32Path
# This module implements the SLIP-0013 authentication using a deterministic hierarchy, see
# https://github.com/satoshilabs/slips/blob/master/slip-0013.md.
async def sign_identity(ctx: wire.Context, msg: SignIdentity) -> SignedIdentity:
if msg.ecdsa_curve_name is None:
msg.ecdsa_curve_name = "secp256k1"
keychain = await get_keychain(ctx, msg.ecdsa_curve_name, [AlwaysMatchingSchema])
identity = serialize_identity(msg.identity)
await require_confirm_sign_identity(ctx, msg.identity, msg.challenge_visual)
address_n = get_identity_path(identity, msg.identity.index or 0)
node = keychain.derive(address_n)
coin = coininfo.by_name("Bitcoin")
if msg.ecdsa_curve_name == "secp256k1":
# hardcoded bitcoin address type
address: Optional[str] = node.address(coin.address_type)
else:
address = None
pubkey = node.public_key()
if pubkey[0] == 0x01:
pubkey = b"\x00" + pubkey[1:]
seckey = node.private_key()
if msg.identity.proto == "gpg":
signature = sign_challenge(
seckey,
msg.challenge_hidden,
msg.challenge_visual,
"gpg",
msg.ecdsa_curve_name,
)
elif msg.identity.proto == "signify":
signature = sign_challenge(
seckey,
msg.challenge_hidden,
msg.challenge_visual,
"signify",
msg.ecdsa_curve_name,
)
elif msg.identity.proto == "ssh":
signature = sign_challenge(
seckey,
msg.challenge_hidden,
msg.challenge_visual,
"ssh",
msg.ecdsa_curve_name,
)
else:
signature = sign_challenge(
seckey,
msg.challenge_hidden,
msg.challenge_visual,
coin,
msg.ecdsa_curve_name,
)
return SignedIdentity(address=address, public_key=pubkey, signature=signature)
async def require_confirm_sign_identity(
ctx: wire.Context, identity: IdentityType, challenge_visual: Optional[str]
) -> None:
lines: List[TextContent] = []
if challenge_visual:
lines.append(challenge_visual)
lines.append(ui.MONO)
lines.extend(chunks(serialize_identity_without_proto(identity), 18))
proto = identity.proto.upper() if identity.proto else "identity"
text = Text("Sign %s" % proto)
text.normal(*lines)
await require_confirm(ctx, text)
def serialize_identity(identity: IdentityType) -> str:
s = ""
if identity.proto:
s += identity.proto + "://"
if identity.user:
s += identity.user + "@"
if identity.host:
s += identity.host
if identity.port:
s += ":" + identity.port
if identity.path:
s += identity.path
return s
def serialize_identity_without_proto(identity: IdentityType) -> str:
proto = identity.proto
identity.proto = None # simplify serialized identity string
s = serialize_identity(identity)
identity.proto = proto
return s
def get_identity_path(identity: str, index: int) -> Bip32Path:
identity_hash = sha256(pack("<I", index) + identity.encode()).digest()
address_n = [HARDENED | x for x in (13,) + unpack("<IIII", identity_hash[:16])]
return address_n
def sign_challenge(
seckey: bytes,
challenge_hidden: bytes,
challenge_visual: str,
sigtype: Union[str, coininfo.CoinInfo],
curve: str,
) -> bytes:
from trezor.crypto.hashlib import sha256
if curve == "secp256k1":
from trezor.crypto.curve import secp256k1
elif curve == "nist256p1":
from trezor.crypto.curve import nist256p1
elif curve == "ed25519":
from trezor.crypto.curve import ed25519
from apps.common.signverify import message_digest
if sigtype == "gpg":
data = challenge_hidden
elif sigtype == "signify":
if curve != "ed25519":
raise wire.DataError("Unsupported curve")
data = challenge_hidden
elif sigtype == "ssh":
if curve != "ed25519":
data = sha256(challenge_hidden).digest()
else:
data = challenge_hidden
elif isinstance(sigtype, coininfo.CoinInfo):
# sigtype is coin
challenge = (
sha256(challenge_hidden).digest()
+ sha256(challenge_visual.encode()).digest()
)
data = message_digest(sigtype, challenge)
else:
raise wire.DataError("Unsupported sigtype")
if curve == "secp256k1":
signature = secp256k1.sign(seckey, data)
elif curve == "nist256p1":
signature = nist256p1.sign(seckey, data)
elif curve == "ed25519":
signature = ed25519.sign(seckey, data)
else:
raise wire.DataError("Unknown curve")
if curve == "ed25519":
signature = b"\x00" + signature
elif sigtype == "gpg" or sigtype == "ssh":
signature = b"\x00" + signature[1:]
return signature