Skip to content

Commit

Permalink
feat: support webauthn for mfa
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Oct 19, 2021
1 parent 869b4a5 commit e8f4d3c
Show file tree
Hide file tree
Showing 41 changed files with 1,599 additions and 71 deletions.
15 changes: 15 additions & 0 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"testing"
"time"

"github.com/duo-labs/webauthn/webauthn"

"github.com/spf13/cobra"

"github.com/ory/x/jsonschemax"
Expand Down Expand Up @@ -137,6 +139,10 @@ const (
ViperKeyPasswordMaxBreaches = "selfservice.methods.password.config.max_breaches"
ViperKeyIgnoreNetworkErrors = "selfservice.methods.password.config.ignore_network_errors"
ViperKeyTOTPIssuer = "selfservice.methods.totp.config.issuer"
ViperKeyWebAuthnRPDisplayName = "selfservice.methods.webauthn.config.rp.display_name"
ViperKeyWebAuthnRPID = "selfservice.methods.webauthn.config.rp.id"
ViperKeyWebAuthnRPOrigin = "selfservice.methods.webauthn.config.rp.origin"
ViperKeyWebAuthnRPIcon = "selfservice.methods.webauthn.config.rp.issuer"
ViperKeyVersion = "version"
Argon2DefaultMemory = 128 * bytesize.MB
Argon2DefaultIterations uint32 = 1
Expand Down Expand Up @@ -996,6 +1002,15 @@ func (p *Config) PasswordPolicyConfig() *PasswordPolicy {
}
}

func (p *Config) WebAuthnConfig() *webauthn.Config {
return &webauthn.Config{
RPDisplayName: p.p.String(ViperKeyWebAuthnRPDisplayName),
RPID: p.p.String(ViperKeyWebAuthnRPID),
RPOrigin: p.p.String(ViperKeyWebAuthnRPOrigin),
RPIcon: p.p.String(ViperKeyWebAuthnRPIcon),
}
}

func (p *Config) HasherPasswordHashingAlgorithm() string {
configValue := p.p.StringF(ViperKeyHasherAlgorithm, DefaultPasswordHashingAlgorithm)
switch configValue {
Expand Down
3 changes: 3 additions & 0 deletions driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"sync"
"time"

"github.com/ory/kratos/selfservice/strategy/webauthn"

"github.com/ory/kratos/selfservice/strategy/lookup"

"github.com/ory/kratos/selfservice/strategy/totp"
Expand Down Expand Up @@ -263,6 +265,7 @@ func (m *RegistryDefault) selfServiceStrategies() []interface{} {
profile.NewStrategy(m),
link.NewStrategy(m),
totp.NewStrategy(m),
webauthn.NewStrategy(m),
lookup.NewStrategy(m),
}
}
Expand Down
78 changes: 78 additions & 0 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,84 @@
}
}
},
"webauthn": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"title": "Enables the WebAuthn method",
"default": false
},
"config": {
"type": "object",
"title": "WebAuthn Configuration",
"properties": {
"rp": {
"title": "Relying Party (RP) Config",
"required": [
"id",
"display_name"
],
"properties": {
"display_name": {
"type": "string",
"title": "Relying Party Display Name",
"description": "An name to help the user identify this RP.",
"examples": [
"Ory Foundation"
]
},
"id": {
"type": "string",
"title": "Relying Party Identifier",
"description": "The id must be a subset of the domain currently in the browser.",
"examples": [
"ory.sh"
]
},
"origin": {
"type": "string",
"title": "Relying Party Origin",
"description": "An explicit RP origin. If left empty, this defaults to `id`.",
"format": "uri",
"examples": [
"https://www.ory.sh/login"
]
},
"icon": {
"type": "string",
"title": "Relying Party Icon",
"description": "An icon to help the user identify this RP.",
"format": "uri",
"examples": [
"https://www.ory.sh/an-icon.png"
]
}
},
"type": "object"
}
},
"additionalProperties": false
}
},

"if": {
"properties": {
"enabled": {
"const": true
}
},
"required": [
"enabled"
]
},
"then": {
"required": [
"config"
]
}
},
"oidc": {
"type": "object",
"title": "Specify OpenID Connect and OAuth2 Configuration",
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/davecgh/go-spew v1.1.1
github.com/davidrjonas/semver-cli v0.0.0-20190116233701-ee19a9a0dda6
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc
github.com/fatih/color v1.9.0
github.com/form3tech-oss/jwt-go v3.2.2+incompatible
github.com/ghodss/yaml v1.0.0
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX
github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 h1:Puu1hUwfps3+1CUzYdAZXijuvLuRMirgiXdf3zsM2Ig=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
Expand Down Expand Up @@ -321,6 +323,7 @@ github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70d
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
Expand All @@ -344,6 +347,8 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc h1:mLNknBMRNrYNf16wFFUyhSAe1tISZN7oAfal4CZ2OxY=
github.com/duo-labs/webauthn v0.0.0-20210727191636-9f1b88ef44cc/go.mod h1:/X2OJiJxjQ7alqWZqX9EtBTmZc+4qQ0LvZ1k5wP67RM=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
Expand Down Expand Up @@ -387,6 +392,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
Expand Down Expand Up @@ -645,6 +652,8 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
Expand Down Expand Up @@ -1258,6 +1267,7 @@ github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiB
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
Expand Down Expand Up @@ -1396,6 +1406,8 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
Expand Down
21 changes: 21 additions & 0 deletions schema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ func NewTOTPVerifierWrongError(instancePtr string) error {
})
}

func NewWebAuthnVerifierWrongError(instancePtr string) error {
t := text.NewErrorValidationTOTPVerifierWrong()
return errors.WithStack(&ValidationError{
ValidationError: &jsonschema.ValidationError{
Message: t.Text,
InstancePtr: instancePtr,
},
Messages: new(text.Messages).Add(t),
})
}

func NewLookupAlreadyUsed() error {
t := text.NewErrorValidationLookupAlreadyUsed()
return errors.WithStack(&ValidationError{
Expand Down Expand Up @@ -215,3 +226,13 @@ func NewNoTOTPDeviceRegistered() error {
Messages: new(text.Messages).Add(text.NewErrorValidationNoTOTPDevice()),
})
}

func NewNoWebAuthnRegistered() error {
return errors.WithStack(&ValidationError{
ValidationError: &jsonschema.ValidationError{
Message: `you have no WebAuthn device set up`,
InstancePtr: "#/",
},
Messages: new(text.Messages).Add(text.NewErrorValidationNoWebAuthnDevice()),
})
}
20 changes: 13 additions & 7 deletions selfservice/flow/settings/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,27 @@ func sortNodes(n node.Nodes, schemaRef string) error {
node.ProfileGroup,
node.PasswordGroup,
node.OpenIDConnectGroup,
node.TOTPGroup,
node.LookupGroup,
node.WebAuthnGroup,
node.TOTPGroup,
}),
node.SortUseOrder([]string{
// TOTP
node.TOTPSecretKey,
node.TOTPQR,
node.TOTPUnlink,
node.TOTPCode,

// Lookup
node.LookupReveal,
node.LookupRegenerate,
node.LookupCodes,
node.LookupConfirm,

// Lookup
node.WebAuthnRemove,
node.WebAuthnRegisterDisplayName,
node.WebAuthnRegister,

// TOTP
node.TOTPSecretKey,
node.TOTPQR,
node.TOTPUnlink,
node.TOTPCode,
}),
)
}
13 changes: 13 additions & 0 deletions selfservice/strategy/webauthn/.schema/login.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/webauthn/login.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"csrf_token": {
"type": "string"
},
"webauthn_login": {
"type": "string"
}
}
}
43 changes: 43 additions & 0 deletions selfservice/strategy/webauthn/.schema/settings.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/webauthn/settings.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"csrf_token": {
"type": "string"
},
"method": {
"type": "string"
},
"webauthn_register": {
"type": "string"
},
"webauthn_register_displayname": {
"type": "string"
},
"webauthn_remove": {
"type": "string"
}
},
"if": {
"properties": {
"webauthn_register": {
"minLength": 1
}
},
"required": [
"webauthn_register"
]
},
"then": {
"properties": {
"webauthn_register_displayname": {
"minLength": 1
}
},
"required": [
"webauthn_register_displayname"
]
}
}

64 changes: 64 additions & 0 deletions selfservice/strategy/webauthn/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package webauthn

import (
"time"

"github.com/duo-labs/webauthn/webauthn"
)

// CredentialsConfig is the struct that is being used as part of the identity credentials.
type CredentialsConfig struct {
// List of webauthn credentials.
Credentials Credentials `json:"credentials"`
}

type Credentials []Credential

func CredentialFromWebAuthn(credential *webauthn.Credential) *Credential {
return &Credential{
ID: credential.ID,
PublicKey: credential.PublicKey,
AttestationType: credential.AttestationType,
Authenticator: Authenticator{
AAGUID: credential.Authenticator.AAGUID,
SignCount: credential.Authenticator.SignCount,
CloneWarning: credential.Authenticator.CloneWarning,
},
}
}

func (c Credentials) ToWebAuthn() []webauthn.Credential {
result := make([]webauthn.Credential, len(c))
for k := range c {
result[k] = *c[k].ToWebAuthn()
}
return result
}

func (c *Credential) ToWebAuthn() *webauthn.Credential {
return &webauthn.Credential{
ID: c.ID,
PublicKey: c.PublicKey,
AttestationType: c.AttestationType,
Authenticator: webauthn.Authenticator{
AAGUID: c.Authenticator.AAGUID,
SignCount: c.Authenticator.SignCount,
CloneWarning: c.Authenticator.CloneWarning,
},
}
}

type Credential struct {
ID []byte `json:"id"`
PublicKey []byte `json:"public_key"`
AttestationType string `json:"attestation_type"`
Authenticator Authenticator `json:"authenticator"`
DisplayName string `json:"display_name"`
AddedAt time.Time `json:"added_at"`
}

type Authenticator struct {
AAGUID []byte `json:"aaguid"`
SignCount uint32 `json:"sign_count"`
CloneWarning bool `json:"clone_warning"`
}

0 comments on commit e8f4d3c

Please sign in to comment.