gitea源码

webauthn.go 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "context"
  6. "fmt"
  7. "strings"
  8. "code.gitea.io/gitea/models/db"
  9. "code.gitea.io/gitea/modules/timeutil"
  10. "code.gitea.io/gitea/modules/util"
  11. "github.com/go-webauthn/webauthn/protocol"
  12. "github.com/go-webauthn/webauthn/webauthn"
  13. )
  14. // ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error.
  15. type ErrWebAuthnCredentialNotExist struct {
  16. ID int64
  17. CredentialID []byte
  18. }
  19. func (err ErrWebAuthnCredentialNotExist) Error() string {
  20. if len(err.CredentialID) == 0 {
  21. return fmt.Sprintf("WebAuthn credential does not exist [id: %d]", err.ID)
  22. }
  23. return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID)
  24. }
  25. // Unwrap unwraps this as a ErrNotExist err
  26. func (err ErrWebAuthnCredentialNotExist) Unwrap() error {
  27. return util.ErrNotExist
  28. }
  29. // IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist.
  30. func IsErrWebAuthnCredentialNotExist(err error) bool {
  31. _, ok := err.(ErrWebAuthnCredentialNotExist)
  32. return ok
  33. }
  34. // WebAuthnCredential represents the WebAuthn credential data for a public-key
  35. // credential conformant to WebAuthn Level 1
  36. type WebAuthnCredential struct {
  37. ID int64 `xorm:"pk autoincr"`
  38. Name string
  39. LowerName string `xorm:"unique(s)"`
  40. UserID int64 `xorm:"INDEX unique(s)"`
  41. CredentialID []byte `xorm:"INDEX VARBINARY(1024)"`
  42. PublicKey []byte
  43. AttestationType string
  44. AAGUID []byte
  45. SignCount uint32 `xorm:"BIGINT"`
  46. CloneWarning bool
  47. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  48. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  49. }
  50. func init() {
  51. db.RegisterModel(new(WebAuthnCredential))
  52. }
  53. // TableName returns a better table name for WebAuthnCredential
  54. func (cred WebAuthnCredential) TableName() string {
  55. return "webauthn_credential"
  56. }
  57. // UpdateSignCount will update the database value of SignCount
  58. func (cred *WebAuthnCredential) UpdateSignCount(ctx context.Context) error {
  59. _, err := db.GetEngine(ctx).ID(cred.ID).Cols("sign_count").Update(cred)
  60. return err
  61. }
  62. // BeforeInsert will be invoked by XORM before updating a record
  63. func (cred *WebAuthnCredential) BeforeInsert() {
  64. cred.LowerName = strings.ToLower(cred.Name)
  65. }
  66. // BeforeUpdate will be invoked by XORM before updating a record
  67. func (cred *WebAuthnCredential) BeforeUpdate() {
  68. cred.LowerName = strings.ToLower(cred.Name)
  69. }
  70. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  71. func (cred *WebAuthnCredential) AfterLoad() {
  72. cred.LowerName = strings.ToLower(cred.Name)
  73. }
  74. // WebAuthnCredentialList is a list of *WebAuthnCredential
  75. type WebAuthnCredentialList []*WebAuthnCredential
  76. // newCredentialFlagsFromAuthenticatorFlags is copied from https://github.com/go-webauthn/webauthn/pull/337
  77. // to convert protocol.AuthenticatorFlags to webauthn.CredentialFlags
  78. func newCredentialFlagsFromAuthenticatorFlags(flags protocol.AuthenticatorFlags) webauthn.CredentialFlags {
  79. return webauthn.CredentialFlags{
  80. UserPresent: flags.HasUserPresent(),
  81. UserVerified: flags.HasUserVerified(),
  82. BackupEligible: flags.HasBackupEligible(),
  83. BackupState: flags.HasBackupState(),
  84. }
  85. }
  86. // ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials
  87. func (list WebAuthnCredentialList) ToCredentials(defaultAuthFlags ...protocol.AuthenticatorFlags) []webauthn.Credential {
  88. // TODO: at the moment, Gitea doesn't store or check the flags
  89. // so we need to use the default flags from the authenticator to make the login validation pass
  90. // In the future, we should:
  91. // 1. store the flags when registering the credential
  92. // 2. provide the stored flags when converting the credentials (for login)
  93. // 3. for old users, still use this fallback to the default flags
  94. defAuthFlags := util.OptionalArg(defaultAuthFlags)
  95. creds := make([]webauthn.Credential, 0, len(list))
  96. for _, cred := range list {
  97. creds = append(creds, webauthn.Credential{
  98. ID: cred.CredentialID,
  99. PublicKey: cred.PublicKey,
  100. AttestationType: cred.AttestationType,
  101. Flags: newCredentialFlagsFromAuthenticatorFlags(defAuthFlags),
  102. Authenticator: webauthn.Authenticator{
  103. AAGUID: cred.AAGUID,
  104. SignCount: cred.SignCount,
  105. CloneWarning: cred.CloneWarning,
  106. },
  107. })
  108. }
  109. return creds
  110. }
  111. // GetWebAuthnCredentialsByUID returns all WebAuthn credentials of the given user
  112. func GetWebAuthnCredentialsByUID(ctx context.Context, uid int64) (WebAuthnCredentialList, error) {
  113. creds := make(WebAuthnCredentialList, 0)
  114. return creds, db.GetEngine(ctx).Where("user_id = ?", uid).Find(&creds)
  115. }
  116. // ExistsWebAuthnCredentialsForUID returns if the given user has credentials
  117. func ExistsWebAuthnCredentialsForUID(ctx context.Context, uid int64) (bool, error) {
  118. return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
  119. }
  120. // GetWebAuthnCredentialByName returns WebAuthn credential by id
  121. func GetWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*WebAuthnCredential, error) {
  122. cred := new(WebAuthnCredential)
  123. if found, err := db.GetEngine(ctx).Where("user_id = ? AND lower_name = ?", uid, strings.ToLower(name)).Get(cred); err != nil {
  124. return nil, err
  125. } else if !found {
  126. return nil, ErrWebAuthnCredentialNotExist{}
  127. }
  128. return cred, nil
  129. }
  130. // GetWebAuthnCredentialByID returns WebAuthn credential by id
  131. func GetWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) {
  132. cred := new(WebAuthnCredential)
  133. if found, err := db.GetEngine(ctx).ID(id).Get(cred); err != nil {
  134. return nil, err
  135. } else if !found {
  136. return nil, ErrWebAuthnCredentialNotExist{ID: id}
  137. }
  138. return cred, nil
  139. }
  140. // HasWebAuthnRegistrationsByUID returns whether a given user has WebAuthn registrations
  141. func HasWebAuthnRegistrationsByUID(ctx context.Context, uid int64) (bool, error) {
  142. return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
  143. }
  144. // GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
  145. func GetWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) {
  146. cred := new(WebAuthnCredential)
  147. if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil {
  148. return nil, err
  149. } else if !found {
  150. return nil, ErrWebAuthnCredentialNotExist{CredentialID: credID}
  151. }
  152. return cred, nil
  153. }
  154. // CreateCredential will create a new WebAuthnCredential from the given Credential
  155. func CreateCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) {
  156. c := &WebAuthnCredential{
  157. UserID: userID,
  158. Name: name,
  159. CredentialID: cred.ID,
  160. PublicKey: cred.PublicKey,
  161. AttestationType: cred.AttestationType,
  162. AAGUID: cred.Authenticator.AAGUID,
  163. SignCount: cred.Authenticator.SignCount,
  164. CloneWarning: false,
  165. }
  166. if err := db.Insert(ctx, c); err != nil {
  167. return nil, err
  168. }
  169. return c, nil
  170. }
  171. // DeleteCredential will delete WebAuthnCredential
  172. func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) {
  173. had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{})
  174. return had > 0, err
  175. }
  176. // WebAuthnCredentials implements the webauthn.User interface
  177. func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) {
  178. dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID)
  179. if err != nil {
  180. return nil, err
  181. }
  182. return dbCreds.ToCredentials(), nil
  183. }