Nexa Identity Protocol (Login)
Overview
Passwords are a pain and are insecure. If you use the internet, you have no doubt experienced the pain. And much has been written about how human-interaction problems create insecurities -- people choose short or simple passwords, repeat passwords across time and sites, write passwords down, etc. These problems are addressed by password manager programs, but these programs have their own problems -- passwords are either backed up on the "cloud" (less secure), or cannot be easily transferred between computers or browsers on the same computer.
We all know that entering your password into an untrusted computer is dangerous, and that entering your password into a trusted computer on an untrusted or insecure web site is dangerous. But homograph attacks can make this very hard to see, and typosquatting can fool the unwary.
If a password needs to be entered, it should be entered only into hardware that is provided by (and presumably more trusted by) the person who knows the password.
Entering your password into a trusted computer on a trusted, secure web site is not entirely secure -- an IT professional working inside the organization could serve you a patched web site with a bit of javascript that sends him/her your password. Or a deep dependency could insert some malicious code that goes live when the site's software is upgraded to the latest versions.
These problems mean that password entry handling software should not be separately implemented by every web site and served every time the web site is accessed. Web sites are uniquely unsuited for password entry handling due to the volume of different sites, rapid update cycles, and the potential delivery of new code on every click. This makes independent validation of the software infeasible. Instead, a few software implementations should handle password entry for many sites, and should be updated rarely.
Fundamentally, the act of entering passwords into untrusted software on untrusted machines creates a large number of vulnerabilities.
Let's fix these problems.
Operation
This system is implemented in an crypto-identity application or as a sub-function of a Nexa wallet, most commonly located on a phone or tablet (mobile device). Password entry unlocks the app (and potentially is part of the underlying private key). The app then logs the user into a web site in one of 2 modes: using a passwordless public/private key login scheme (more secure), or as a traditional password manager.
In password manager mode, a browser plugin can be used used to communicate between the browser and phone. Password manager mode exists to support existing web sites, unmodified, whereas the public/private key login requires server-side changes. The password manager mode is much less secure than public/private key cryptography. The vulnerabilities that a password manager solves and does not solve are well known so this operation mode is not the focus of this document. Unless specifically indicated, the rest of this document (for example, protocol operation and claims of problems solved) pertains to the public/private key login method.
During login, the web site provides the user's crypto-identity app with a challenge string consisting of random data. This challenge string is transmitted to the user's app visually via a QR code, directly via a link, or via a browser plug-in that has previously been securely introduced to the crypto-identity app. After an optional password entry unlocks the crypto-identity app, and/or a site-specific very secure password is entered, the app generates a private key based on a key derived from the wallet's master private key and the optional site-specific password. The app uses this key to sign a message consisting of the website's domain, the operation being performed (login), and the challenge string. This is communicated directly from the crypto-identity app to the web site server (bypassing the client machine entirely) via a HTTP get request to effect a log in.
Problems Solved
In the "overview" section I identified a few problems with current log in schemes. This section lists these problems and how they are solved:
- Remembering passwords: Secret keys are stored on the user's phone -- the only password that must be remembered is to unlock the phone and/or the Nexa wallet app, for low security websites.
- Repeating passwords: The login process does not use passwords, or provides a different password to each web site (when in password-manager mode).
- Entering passwords into untrusted machines or software: Login is passwordless -- the only passwords needed unlocks the crypto-identity app or is used to derive a private key on the users phone . This is entered into the user's own device, running dedicated software. This is not a guarantee of security -- but it is arguably a lot more secure than running arbitrary software on unknown machines.
- Backing up password manager data: The app uses a Hierarchical Deterministic (HD) wallet. This means that the wallet can be backed up once upon creation by saving a 12 word recovery phrase. This means that it is possible to recover logins that did not exist at the time this phrase was written down, so it is unnecessary to back up the wallet periodically. This makes it unnecessary to store this sensitive data in the "cloud" where it could be stolen.
- Use on multiple machines: Password data does not need to be synchronized across machines as is required with password managers, since it can be located on a portable device. Regardless, it is possible to place the user's wallet on multiple devices (or to upgrade a device), simply by entering the recovery phrase into the app running on a new device.
- Homograph/typosquatting attacks: A machine is not fooled by visually similar domain names so it will not recognize the web site that is requesting a login. Even if the user is fooled into registering with the attacking site, a different public key is provided to the attacker than the actual site, preventing replay and some forms of man-in-the-middle attacks. The wallet also incorporates the domain name of the requesting web site into the message it signs, so this signature cannot be successfully replayed on the legitimate web site. As stated in the caveats, a combined homograph/typosquatting and MITM attack is still possible to gain a session with the targeted server as the targeted user, if a QR code is used rather than a plugin. However, the username and password is not compromised, and the engineering capability to pull off this attack is higher than current attacks.
- login using an insecure/public computer: In the challenge-type login, the secret key never leaves your phone, and the signed message is not communicated through the insecure computer, so a compromised computer cannot access it. There is also no chance that the user accidentally saves his login information in the browser.
- Web site bugs, and malicious code injection: The crypto-identity app is ideally open source, updated rarely, and allows sign-in to many sites. This makes code quality issues and malicious code injection much easier to control than for web sites. As a challenge-response login, the protocol never communicates a password that can be stolen, and the signed challenge cannot be used to access any other web site or session within this site.
Caveats
“A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools.” ― Douglas Adams, Mostly Harmless
Like a password manager, all your passwords are in one place. Compromising the password protecting this app, the backup key, or the app itself will expose all passwords.
Storing passwords in an app on an individual's personal mobile phone may give attackers greater opportunity for physical access than a using a computer that does not leave the home. However, the proliferation of cheap Android devices allows a dedicated device to be used rather than a phone.
If two factor authentication (2FA) is also enabled, use a yublikey or other dedicated device. Loading both crypto-identity software and 2FA software on the same device defeats the purpose of 2FA. However, if the crypto-identity software requires a strong password that it incorporates into its private key, 2 "factors" are required for access. However this is not quite as secure as traditional 2FA because an attack must compromise only 1 device rather than 2.
A combination homograph/MITM attack is possible if QR codes are used to send the login offer to the phone. In this scenario, Alice wants to connect to Sally, but is fooled via a homograph or typosquatting URL into connecting to Mallory. Mallory contacts Sally, is allocated a session, and receives the login page. Mallory presents it to Alice. Alice uses the crypto-identity app to scan the provided QR code and log in by communicating directly to Sally (if a plugin is used, the plugin would detect that the domain in the login URI does not match the domain serving the login offer, defeating the attack). This authenticates Mallory's session, allowing him access for as long as the session is open. Note that this attack is also possible using traditional login techniques, and in that situation Mallory gains Alice's username and password. One possible solution to this problem is to have Alice sign every http get and put request she sends to Sally, and of course Sally validates these signatures. However, as we see in https attacks, homograph or typosquatting web sites can easily register a with a certificate authority.
In some situations, it is possible for a remote attacker to request a login to your app. For example, s/he could send you a photo of the login QR code, or a paired browser plugin might automatically connect to your crypto-identity app. This could be a feature to give someone temporary access. But don't OK a login if you are not sure where its coming from!
Protocol Description
Login
sequenceDiagram
participant Phone
participant Browser
participant Server
Browser ->> Server: HTTP get
Server ->> Browser: Login page (HTTP response)
Browser ->> Phone: Login Offer (via QR code or plugin)
Phone ->> Server: Login offer response (signed challenge)
Server ->> Phone: Login OK/ERROR
alt login ok
Server ->> Browser: Redirect to home page
else unknown identity
Server ->> Browser: Opt redirect to signup page
end
Login Offer
The server provides a URI (or a QR-encoded URI) of the following format (bold indicated variable fields):
nexid://domain/path?op=login&proto=protocol&chal=challengeString&cookie=cookie
Fields:
domain: domain name of the site to log into (e.g. "spec.nexa.org". Also include the port using the standard :number format if it is not the default http port. Use ":443" to request a https response. In rare cases in the followin APIs may not have a domain or the QR code creator may not need need a reply. In this case, the domain and path can be encoded as the string "_". In particular, the "sign" API can be used to sign a message but leave it to the user/light client to actually use the signature.
path: where to send the response (e.g. "/login/auto")
protocol: (optional) The protocol to use to respond to this offer. Currently http or https is supported. If not provided, the protocol will default to the protocol the offer came in on (if http or https), then http.
challengeString: an arbitrary string chosen by the server that is unique for every login and contains sufficient entropy to make repeats incredibly unlikely. The challenge string MUST only contain ASCII alphanumeric characters (A-Z, a-z, 0-9, and _).
cookie: arbitrary data chosen by the server that will be sent back to the server during the login response. For web sites, this is often the originating session identifier since the login response will likely arrive in a different session.
All fields MUST be supplied. If a challengeString is supplied the wallet may respond with either a plain signature OR with a signed challenge transaction (with that string in the data field of the tx).
Note that the site asking for a login does not and does not need to supply a username. The cryptoidentity app uses the identity of the site to determine what address it uses for login. If the user wants to have multiple accounts in the site, they either need a cryptoidentity app that supports that functionality or to use different instances of the app.
Communication
If the login is to a web site, the server presents the login offer as a link and optionally a QR code. The server listens for a HTTP or HTTPS "get" message to domain/path, described below.
For other server types, communication of this URI is beyond the scope of this document.
The crypto-identity app should register itself as the handler for the "nexid" URI protocol identifier in the fashion appropriate to the platform it runs on. This will ensure that clicking on a web link or the generic processing of this QR code will open the crypto-identity app.
On desktop platforms, a browser plugin may register the "nexid" URI, and be used to proxy the nexid URIs to a paired mobile phone.
Login Offer Response
For entities that support the HTTP protocol, the crypto-identity app creates and issues a http or https "get" request of one of the following formats:
http://domain[:port]/path?op=login&addr=identity address&sig=signature
http://domain[:port]/path?op=login&addr=identity address&ctxsig=signedChallengeTx
domain, port, path: These parameters are provided by the login offer.
identity address: This is the Nexa address that this site knows as your identity. This MUST be a Nexa P2PKH (pay-to-pub-key-hash) format address (not a script template).
signature: The URL-encoded signature calculated as described in the Signature Computation section of this document.
signedChallengeTx: The hex-encoded signed challenge transaction.
Additional fields are ignored (not an error).
A server MUST accept either form regardless of how the request was formed. A device may choose to provide a response in either form. Simpler devices may opt for a simple signature, but since some addresses do not have plain signature forms, general purpose devices must be able to provide signatures in challenge transaction form.
Server response to a login attempt
The server MUST reply to the Login Response message with the following error codes and strings:
200: "login accepted" Success! Along with sending this response to the crypto-identity app, the server SHOULD redirect or update the client's browser away from the login screen.
200: "bad signature" The signature is incorrect
301/302: "redirect" Clients should handle http redirects in the expected fashion. A common redirect is http to https if the client chose incorrectly.
404: "unknown session" The cookie does not match any known session. The session has probably expired in the server and the user needs to reload the web page.
404: "unknown operation" The desired operation is not supported by this server
401: "unknown identity" The supplied address does not match any registered users. The server MAY redirect or update the client's browser away from the login screen, to a new user signup screen. But the server SHOULD still accept at least 33 login attempts (the wallet recovery process requires testing a few possible identities) before invalidating the login offer (it is best to not invalidate the login offer at all until the session expires).
Registration / Information
This protocol requests and provides signed data, alongside the normal "login" challenge string. A common use for this functionality is registration, however, this protocol can be used to gather information during every login (so the server does not need to store it), to refresh or modify saved registration information, or to request additional information as needed.
Registration Offer
nexid://domain/path?op=reg&proto=protocol&chal=challengeString&cookie=cookie [&hdl=dataSpec
nexid://domain/path?op=info&proto=protocol&chal=challengeString&cookie=cookie[&hdl=dataSpec
Values:
dataSpec: underscore delimited field specifying parameters/constraints on the requested data. Ignore unknown specifiers: "o" = optional field:
"m" = mandatory field: The operation will not succeed if this field is not provided
"r" = recommended: If this field is not provided, registration will succeed but significant site functionality will be missing. User may need to provide the data later. For example an online store might set the "postal" field as recommended during initial account creation -- a postal address must be entered when a purchase is made but the site can be used to browse products first. When the purchase is made, another "reg" QR code could be displayed with the "postal" field set to mandatory.
Fields:
op: The operation may be "reg" or "info". There is no semantic difference, but clients MAY change how information is presented based on this choice. Typically "info" is used to request more information from a user that has already registered. Whereas "reg" is used to register (first time login) or login and provide information.
domain: domain name of the site to log into (e.g. "spec.nexa.org". Also include the port using the standard :number format if it is not the default http port. Use ":443" to request a https response.
path: where to send the response (e.g. "/login/auto")
challengeString: an arbitrary string chosen by the server that is unique for every login and contains sufficient entropy to make repeats incredibly unlikely. The challenge string MUST only contain ASCII alphanumeric characters (A-Z, a-z, 0-9, and _).
cookie: arbitrary data chosen by the server that will be sent back to the server during the login response. For web sites, this is often the originating session identifier since the login response will likely arrive in a different session.
protocol: (optional) The protocol to use to respond to this offer. Currently http or https is supported. If not provided, the protocol will default to the protocol the offer came in on (if http or https), then http.
If any of these parameters is not included, the registration does not want this data.
hdl: (Optional) username or handle to use at this site
realname: (Optional) real name
postal: (Optional) mailing address
billing: (Optional) billing address
dob: (Optional) birthday
attest: (Optional) attestations
ava: (Optional) avatar
sm: (Optional) social media information
ph: (Optional) provide phone number
Registration Offer Response
The crypto-identity app responds with a http or https "post" request of the following format.
http://**domain**[**:port**]/**path**?cookie=**cookie**
The cookie is provided in the URL path so that the requester can connect the response to their request before parsing to POST body.
POST contentType "application/json":
{
'op':'reg',
'cookie':**cookie from request**,
'addr':**identity address**,
'sig':**signature**, *OR* 'ctxsig': **signedChallengeTx**
'hdl':**hdl**,
'realname':**realname**,
'postal':**postal address**,
'billing':**billing**,
'dob':**birthday**,
'ava':**avatar**,
'attest':**attestationIdentifierAndSignature**,
'ph':**phone number**
'sm':**social media**
}
cookie: return the string provided in the registration offer
domain, port, path: These parameters are provided by the login offer.
identity address: This is the address to be registered as your user identifier. This MUST be sent in Nexa pay-2-pub-key-hash (P2PKH) format.
signature: The URL-encoded signature calculated as described in the Signature Computation section of this document. signedChallengeTx: The hex-encoded signed challenge transaction.
A registration offer response should not include any data that the offer did not request. However response processors MUST tolerate the inclusion of extra data fields, both defined and undefined.
hdl: The username or handle (that is, the display name) desired by this registration. + format is a single string
realname: The user's actual name + format is "first [middle] last" (first{space}optional middle{space}last"
postal address: The user's postal address
billing: The user's billing address
ava: The user's avatar image
+ hex-encoded if it is in a binary format, otherwise text data. The data may be a Nexa NFT in group address form (that is an address of the form
social media: Social media information + Social media information is specified as a string of service:handle, e.g. "twitter:janeDoe, keybase:janieD". Ignore whitespace around the comma or colon.
Server response to a login attempt
Error codes are as specified in the "get" login attempt.
Sign
This protocol requests that a user-understandable message be signed. The message should be presented to the user by the device and signed only when the user agrees.
Request
nexid://domain/path?op=sign&proto=protocol&sign=message&cookie=cookie[&addr=address][&reply=false]
nexid://domain/path?op=sign&proto=protocol&signhex=messageHex&cookie=cookie[&addr=address][&reply=false]
Fields:
domain, path, proto, and cookie have the same definitions as above.
addr: Optional address. Applications should rely on wallets to know the identity (address) relevant to this domain and path, so an address should not be provided in normal use. If an application wants proof a different identity, this optional field may be included. For example, an application may want you to prove that you created a token by signing with the genesis address of that token. If an address is provided (and the wallet user OKs the request), the wallet will sign using the requested address.
message: String to be signed. This string MUST be a UTF-8 URL-form-encoded string. The actual message signed will be the UTF-8 string obtained after URL-form decoding.
messageHex: Hex-encoded binary data to be signed. The actual message signed will be the binary data after hex decoding.
Response
For entities that support the HTTP protocol, the crypto-identity app creates and issues a http or https "get" request of the following format:
http://domain[:port]/path?op=sign&addr=identity address&sig=signature&cookie=cookie
domain, port, path,cookie: These parameters are provided by the signing request.
identity address: This is the Nexa address that was used to sign the message. If a specific address was not requested, this MUST be a Nexa P2PKH (pay-to-pub-key-hash) format address (not a script template).
signature: The URL-encoded bitcoin-standard signature of the passed message.
reply: similar to encoding _ as the domain, if reply exists and is not the string "true" then no reply should be attempted
Additional fields are ignored (not an error).
Crypto-identity App Operation
Identity management
The crypto-identity app has the concept of "common identities" and "unique identities". A common identity is deliberately used across multiple domains to link accounts. A unique identity is probabilistically given to just one web site.
Apps can use any method to organize identities. However, to ensure seed recovery compatibility, apps SHOULD follow these procedures to choose a private key or username/password for every log in.
Choosing a public/private key for challenge based authentication
First use BIP32 compliant HD wallets with the following BIP44 compliant derivation path:
m/44'/0x1c3b1c3b'/0'/0/index
If the user enters a site-specific passphrase then the actual private key is the SHA256( derivation key + passphrase), otherwise the derivation key itself is used as the private key.
index: use 0-31 if a "common identity" is desired, otherwise, derive an index from the server domain name using the following algorithm:
uniquifier = DeriveChildKey(m/44'/0x1c3b1c3b'/0'/0/0xffffffff)
bytes = SHA256(SHA256(domain name + uniquifier)) # where + is string concatenation
index = bytes[0]&~31 + bytes[1]*256 + bytes[2]*256*256 + hash[3]*256*256*256
This organization provides 32 possible "common identities", and an algorithmic way to recover unique identities from the HD wallet seed. If the user cannot remember whether a common or unique identity was used, it is possible to try logging in with all 33 identities. In practice it is unlikely that a user will need more than a few common identities, so an efficient recovery algorithm would be to try the unique identity first and then the common identities starting with 0. But, if implemented simply, this may allow a server to infer a relationship between the common identities -- a paranoid implementation will need to recover identities slowly and through separate networks and sessions. Finally, it should be emphasized that this is only a concern for the last-resort option of key recovery from seed phrase. A privacy conscious user should back up his wallet regularly to avoid this situation, or use unique identities and a single common identity.
Choosing a username/password for traditional authentication
TBD
Challenge Transaction Computation
A challenge transaction will be generated as specified in the challenge transaction document, with the following data (in OP_RETURN):
challenger id: domain[:port]_nexid_op_
challenge string: challengeString modified as per the challenge transaction document
domain: the domain name of the login request. To defeat some "man-in-the-middle" attacks, signing apps MUST ensure that this string actually IS the domain to which the response is sent.
port: If the port is 80 (http) or 443 (https) then the port and preceding colon MUST be omitted.
op: This is the operation specified in the request URI.
challengeString: This is the challengeString received in the login offer.
Signature computation
The crypto-identity app signs the following string using a Bitcoin-standard pubkey recoverable ECDSA signature algorithm:
"Bitcoin Signed Message:\ndomain[:port]_nexid_op_challengeString"
The first component of the string to be signed is "Bitcoin Signed Message:\n". This is automatically prepended to every non-transaction message signed by most current Satoshi-derived cryptocurrency wallets and libraries so you may not need to include it.
domain: the domain name of the login request. To defeat some "man-in-the-middle" attacks, signing apps MUST ensure that this string actually IS the domain to which the response is sent.
port: If the port is 80 (http) or 443 (https) then the port and preceding colon MUST be omitted.
op: This is the operation specified in the request URI.
challengeString: This is the challengeString received in the login offer. The signer MUST ensure that it complies with the challengeString rules (as stated above).
The reason the prefixes, domain name, and operation is included in the signature, and the content of the challenge string is restricted, is to make it extremely unlikely that a challenge string can be replayed in another context. For example, if just the challenge string was signed, a server could send the app a valid Nexa transaction as the challenge, potentially stealing money. If the operation was omitted, a server could make it seem like an individual signed a legal document or embarrassing statement. If the domain was omitted, it may be possible to replay the signature on another site.
The string returned in the "sig" field is url-encoded binary data in the "standard" bitcoin signature format RFC 1421: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
Future-proof code will check failed decodings against the "URL and Filename safe" alphabet defined in RFC4648: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
Change History
Aug 2024: Added ability of avatar image to be an NFT address