-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
webauthn signatures #1193
webauthn signatures #1193
Conversation
585efde
to
1d2315b
Compare
1d2315b
to
c59307c
Compare
9dc8ae0
to
70e8a64
Compare
76a069d
to
ecfeba5
Compare
src/Pact/Native.hs
Outdated
|
||
-- With Simplified error messages, map every error to a single string. | ||
Left _ | behavior == Simplified -> | ||
return $ Left "Could not base64-decode string" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
weird indentations here and below on some things
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better now?
Right bs -> case T.decodeUtf8' bs of | ||
Right t -> return $ tStr t | ||
Left _unicodeError -> evalError' i $ "invalid unicode" | ||
Left base64Error -> evalError' i (pretty (T.pack base64Error)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not aligned with line 1349:
...
Left e -> evalError' si (pretty e)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, you mean 1349 calls pretty
on a String
while 1422 calls pretty
on a Text
? That does seem like a discrepancy. I'll change both to call pretty
on a Text
, and recheck replay. Good catch!
WebAuthnSignature | ||
{ authenticatorData | ||
, signature | ||
, clientDataJSON } <- A.eitherDecode (BSL.fromStrict sigBS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
WebAuthnSignature | |
{ authenticatorData | |
, signature | |
, clientDataJSON } <- A.eitherDecode (BSL.fromStrict sigBS) | |
WebAuthnSignature{..} <- A.eitherDecode (BSL.fromStrict sigBS) |
using {-# LANGUAGE RecordWildCards #-}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like NamedFieldPuns because it makes the identifiers being brought into scope explicit. Meaning: if someone reading the code didn't know what authenticationData
is, or where it comes from, NamedFieldPuns
makes it greppable, while RecordWildCards
doesn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, construct/destruct the thing directly since we're not even really punning as much as we are constructing directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like NamedFieldPuns even for vanilla record desructuring too, since it removes the possibility of accidentally naming fields out of order. Meaning, I could write:
WebAuthnSignature sig authData clientData <- decode sigBS
-- (Is this what you mean by destruct the thing directly?)
...
and it would typecheck but be subtly wrong, because I accidentally swapped the field names around for fields that have the same type.
@@ -2104,14 +2104,6 @@ pact> (expect-that "addition" (> 2) (+ 1 2)) | |||
``` | |||
|
|||
|
|||
### format-address {#format-address} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does dropping this have to do with WebAuthn signatures?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dropping it was required after removing a method from the Scheme
typeclass that format-address
required.
We have to remove some methods and associated types from Scheme
to accommodate webauthn. It's believed be to be unused or only used very rarely.
@@ -145,7 +145,6 @@ library | |||
Pact.Types.Command | |||
Pact.Types.Continuation | |||
Pact.Types.Crypto | |||
Pact.Types.ECDSA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we dropping this module because the WebAuthn support supercedes the functionality?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we were modifying the Scheme
typeclass to support webauthn
, another instance of that typeclass, the ETH
scheme (and the Pact.Types.ECDSA
module that supported it) came up as a place to eliminate some effectively-dead code.
@@ -296,7 +296,7 @@ loadSigData fp = do | |||
Left e -> Left $ "Error loading SigData file " <> fp <> ": " <> show e | |||
Right sd -> Right sd | |||
|
|||
addSigToSigData :: SomeKeyPair -> SigData a -> IO (SigData a) | |||
addSigToSigData :: Ed25519KeyPair -> SigData a -> IO (SigData a) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we start integrating more with other changes, would we still want to monomorphize the signature scheme?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some integrations we could make that would introduce new PPK schemes. Depending on the details of how those integrations work, they might not see the changes here in Pact.ApiReq
, since Pact.ApiReq
is only used client-side, e.g. by users signing requests and making API calls to a pact server. A rule of thumb I used in this PR was to be more cautious changing server-side code, but more liberal with client-side code as needed).
A new integration that merely needs signature verification in pact will not be bothered by the monomorphization happening in client code here. But an integration that also wanted client-side support would indeed be less convenient to write, after we remove SomeKeyPair
.
import Control.Exception.Safe | ||
import Control.Lens hiding (parts,Fold,contains) | ||
import Control.Monad | ||
import Control.Monad.IO.Class | ||
import qualified Data.Attoparsec.Text as AP | ||
import Data.Bifunctor (first) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't conflict with the import of first
from Control.Arrow
just above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import Control.Arrow hiding (app, first)
Added it to the hiding
so I could have my bifunctor first
.
@@ -28,20 +29,29 @@ import qualified Pact.JSON.Encode as J | |||
|
|||
--------- PPKSCHEME DATA TYPE --------- | |||
|
|||
data PPKScheme = ED25519 | ETH | |||
data PPKScheme = ED25519 | WebAuthn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like there are really 4 PRs hiding in this one:
- Monomorphizing the signature scheme
- Dropping the ETH PPKScheme
- Dropping format-address
- Adding the WebAuthn PPKScheme
I want to hear from @larskuhtz whether we'd like to keep the ETH
scheme, if we're going to be doing bridging with Ethereum...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just 4? Don't forget bumping base64-bytestring! :) That's got to count as 3 all on its own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good, just a few comments made!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! Main note is we should reconsider removing ETH, see comment in Pact.Types.Crypto
.
EDIT scratch that, Eth ECDSA support for Pact payloads is a dead letter. ECDSA support in the context of ethereum compatibility (Solidity ABI, or EVM support) uses a different payload.
Co-authored-by: John Wiegley <johnw@newartisans.com>
a1bcd02
to
7c8fb49
Compare
-- Extract the original challenge from client data. | ||
ClientDataJSON { challenge } <- A.eitherDecode (BSL.fromStrict clientData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit worried by the amount of aeson instances here being used to decode things that are on-chain, but I don't suggest changing this at this point.
publicKey <- first show $ | ||
Serialise.deserialiseOrFail @WA.CosePublicKey | ||
(BSL.fromStrict pubBS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we restrict the algorithm here to just a few to avoid algorithm confusion attacks? I think @EnoF wanted the algorithm "ES256", which has COSE identifier -7
. https://portswigger.net/web-security/jwt/algorithm-confusion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added some code that restricts to -7 or -8. (ECDSA SHA-256, and EdDSA respectively).
I informally tested the restriction by setting the requirement to -1 or -8 temporarily and observing that the happy path signature failed.
I'll hopefully be able to generate a test case that we can check in to the repo soon. But it requires spinning up a server that. implements webauthn to create new sample keys with a different signing algorithm.
-- This test uses example public keys, clientData and authenticatorData from a | ||
-- real WebAuthn browser session from a test user. | ||
verifyWebAuthnSignature :: Spec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do restrict the signing algorithm we should have a test for it
Co-authored-by: Edmund Noble <edmundnoble@users.noreply.github.com>
Co-authored-by: Lars Kuhtz <lars@kadena.io>
This PR adds
WebAuthn
as aPPKScheme
, allowing WebAuthn signatures to be used for user authentication in pact commands.It also removes some things that were hard to maintain alongside
WebAuthn
:Scheme
class are removed.SomeKeyPair
is removed.Scheme
class except_valid
are removed.ETH
scheme and all its support code were removed.Removing those things has several consequences:
Scheme
class switch to monomorphic versions, assuming ED25519format-address
repl builtin is removed (it relied on a deletedScheme
method)The removed methods in
Scheme
are primarily used in client code, for instance when Pact generates a .yaml command file, or when we generate keys in tests. This helps justify the simplification ofScheme
, since we're primarily impacting client code. The server-oriented code paths are relatively unchanged.Implementation
A signature is valid as a WebAuthn signature if
clientDataJSON
,authenticatorData
, andsignature
Clients may issue transactions with webauthn signatures by stringifying a JSON blob according to the spec. An example of such a blob is included in the unit tests.
base64-bytestring upgrade
The
webauthn
library requiresbase64-bytestring > 1.0
, and pact requiresbase64-bytestring < 1.0
, so this PR also bumps thebase64-bytestring
library inpact
(which will require a bump inchainweb-node
, too).Bumping
base64-bytestring
is difficult because the new version produces different error messages than the old version (and in some cases, the new version rejects base64-encoded messages that were accepted by old versions). These error messages end up on chain, so to get hash compatibility for historical blocks, we must emulate the oldbase64-bytestring
's behavior. We encountered this issue only in thedecodeBase64
native and thestr-to-int
native, so the behavior-shimming code is colocated with the native definitions (inNative.hs
).