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.

webauthn.go 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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/webauthn"
  12. "xorm.io/xorm"
  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(session *xorm.Session) {
  72. cred.LowerName = strings.ToLower(cred.Name)
  73. }
  74. // WebAuthnCredentialList is a list of *WebAuthnCredential
  75. type WebAuthnCredentialList []*WebAuthnCredential
  76. // ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials
  77. func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential {
  78. creds := make([]webauthn.Credential, 0, len(list))
  79. for _, cred := range list {
  80. creds = append(creds, webauthn.Credential{
  81. ID: cred.CredentialID,
  82. PublicKey: cred.PublicKey,
  83. AttestationType: cred.AttestationType,
  84. Authenticator: webauthn.Authenticator{
  85. AAGUID: cred.AAGUID,
  86. SignCount: cred.SignCount,
  87. CloneWarning: cred.CloneWarning,
  88. },
  89. })
  90. }
  91. return creds
  92. }
  93. // GetWebAuthnCredentialsByUID returns all WebAuthn credentials of the given user
  94. func GetWebAuthnCredentialsByUID(ctx context.Context, uid int64) (WebAuthnCredentialList, error) {
  95. creds := make(WebAuthnCredentialList, 0)
  96. return creds, db.GetEngine(ctx).Where("user_id = ?", uid).Find(&creds)
  97. }
  98. // ExistsWebAuthnCredentialsForUID returns if the given user has credentials
  99. func ExistsWebAuthnCredentialsForUID(ctx context.Context, uid int64) (bool, error) {
  100. return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
  101. }
  102. // GetWebAuthnCredentialByName returns WebAuthn credential by id
  103. func GetWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*WebAuthnCredential, error) {
  104. cred := new(WebAuthnCredential)
  105. if found, err := db.GetEngine(ctx).Where("user_id = ? AND lower_name = ?", uid, strings.ToLower(name)).Get(cred); err != nil {
  106. return nil, err
  107. } else if !found {
  108. return nil, ErrWebAuthnCredentialNotExist{}
  109. }
  110. return cred, nil
  111. }
  112. // GetWebAuthnCredentialByID returns WebAuthn credential by id
  113. func GetWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) {
  114. cred := new(WebAuthnCredential)
  115. if found, err := db.GetEngine(ctx).ID(id).Get(cred); err != nil {
  116. return nil, err
  117. } else if !found {
  118. return nil, ErrWebAuthnCredentialNotExist{ID: id}
  119. }
  120. return cred, nil
  121. }
  122. // HasWebAuthnRegistrationsByUID returns whether a given user has WebAuthn registrations
  123. func HasWebAuthnRegistrationsByUID(ctx context.Context, uid int64) (bool, error) {
  124. return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
  125. }
  126. // GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
  127. func GetWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) {
  128. cred := new(WebAuthnCredential)
  129. if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil {
  130. return nil, err
  131. } else if !found {
  132. return nil, ErrWebAuthnCredentialNotExist{CredentialID: credID}
  133. }
  134. return cred, nil
  135. }
  136. // CreateCredential will create a new WebAuthnCredential from the given Credential
  137. func CreateCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) {
  138. c := &WebAuthnCredential{
  139. UserID: userID,
  140. Name: name,
  141. CredentialID: cred.ID,
  142. PublicKey: cred.PublicKey,
  143. AttestationType: cred.AttestationType,
  144. AAGUID: cred.Authenticator.AAGUID,
  145. SignCount: cred.Authenticator.SignCount,
  146. CloneWarning: false,
  147. }
  148. if err := db.Insert(ctx, c); err != nil {
  149. return nil, err
  150. }
  151. return c, nil
  152. }
  153. // DeleteCredential will delete WebAuthnCredential
  154. func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) {
  155. had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{})
  156. return had > 0, err
  157. }
  158. // WebAuthnCredentials implementns the webauthn.User interface
  159. func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) {
  160. dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID)
  161. if err != nil {
  162. return nil, err
  163. }
  164. return dbCreds.ToCredentials(), nil
  165. }