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 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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/duo-labs/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() error {
  59. return cred.updateSignCount(db.DefaultContext)
  60. }
  61. func (cred *WebAuthnCredential) updateSignCount(ctx context.Context) error {
  62. _, err := db.GetEngine(ctx).ID(cred.ID).Cols("sign_count").Update(cred)
  63. return err
  64. }
  65. // BeforeInsert will be invoked by XORM before updating a record
  66. func (cred *WebAuthnCredential) BeforeInsert() {
  67. cred.LowerName = strings.ToLower(cred.Name)
  68. }
  69. // BeforeUpdate will be invoked by XORM before updating a record
  70. func (cred *WebAuthnCredential) BeforeUpdate() {
  71. cred.LowerName = strings.ToLower(cred.Name)
  72. }
  73. // AfterLoad is invoked from XORM after setting the values of all fields of this object.
  74. func (cred *WebAuthnCredential) AfterLoad(session *xorm.Session) {
  75. cred.LowerName = strings.ToLower(cred.Name)
  76. }
  77. // WebAuthnCredentialList is a list of *WebAuthnCredential
  78. type WebAuthnCredentialList []*WebAuthnCredential
  79. // ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials
  80. func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential {
  81. creds := make([]webauthn.Credential, 0, len(list))
  82. for _, cred := range list {
  83. creds = append(creds, webauthn.Credential{
  84. ID: cred.CredentialID,
  85. PublicKey: cred.PublicKey,
  86. AttestationType: cred.AttestationType,
  87. Authenticator: webauthn.Authenticator{
  88. AAGUID: cred.AAGUID,
  89. SignCount: cred.SignCount,
  90. CloneWarning: cred.CloneWarning,
  91. },
  92. })
  93. }
  94. return creds
  95. }
  96. // GetWebAuthnCredentialsByUID returns all WebAuthn credentials of the given user
  97. func GetWebAuthnCredentialsByUID(uid int64) (WebAuthnCredentialList, error) {
  98. return getWebAuthnCredentialsByUID(db.DefaultContext, uid)
  99. }
  100. func getWebAuthnCredentialsByUID(ctx context.Context, uid int64) (WebAuthnCredentialList, error) {
  101. creds := make(WebAuthnCredentialList, 0)
  102. return creds, db.GetEngine(ctx).Where("user_id = ?", uid).Find(&creds)
  103. }
  104. // ExistsWebAuthnCredentialsForUID returns if the given user has credentials
  105. func ExistsWebAuthnCredentialsForUID(uid int64) (bool, error) {
  106. return existsWebAuthnCredentialsByUID(db.DefaultContext, uid)
  107. }
  108. func existsWebAuthnCredentialsByUID(ctx context.Context, uid int64) (bool, error) {
  109. return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
  110. }
  111. // GetWebAuthnCredentialByName returns WebAuthn credential by id
  112. func GetWebAuthnCredentialByName(uid int64, name string) (*WebAuthnCredential, error) {
  113. return getWebAuthnCredentialByName(db.DefaultContext, uid, name)
  114. }
  115. func getWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*WebAuthnCredential, error) {
  116. cred := new(WebAuthnCredential)
  117. if found, err := db.GetEngine(ctx).Where("user_id = ? AND lower_name = ?", uid, strings.ToLower(name)).Get(cred); err != nil {
  118. return nil, err
  119. } else if !found {
  120. return nil, ErrWebAuthnCredentialNotExist{}
  121. }
  122. return cred, nil
  123. }
  124. // GetWebAuthnCredentialByID returns WebAuthn credential by id
  125. func GetWebAuthnCredentialByID(id int64) (*WebAuthnCredential, error) {
  126. return getWebAuthnCredentialByID(db.DefaultContext, id)
  127. }
  128. func getWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) {
  129. cred := new(WebAuthnCredential)
  130. if found, err := db.GetEngine(ctx).ID(id).Get(cred); err != nil {
  131. return nil, err
  132. } else if !found {
  133. return nil, ErrWebAuthnCredentialNotExist{ID: id}
  134. }
  135. return cred, nil
  136. }
  137. // HasWebAuthnRegistrationsByUID returns whether a given user has WebAuthn registrations
  138. func HasWebAuthnRegistrationsByUID(uid int64) (bool, error) {
  139. return db.GetEngine(db.DefaultContext).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
  140. }
  141. // GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
  142. func GetWebAuthnCredentialByCredID(userID int64, credID []byte) (*WebAuthnCredential, error) {
  143. return getWebAuthnCredentialByCredID(db.DefaultContext, userID, credID)
  144. }
  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(userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) {
  156. return createCredential(db.DefaultContext, userID, name, cred)
  157. }
  158. func createCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) {
  159. c := &WebAuthnCredential{
  160. UserID: userID,
  161. Name: name,
  162. CredentialID: cred.ID,
  163. PublicKey: cred.PublicKey,
  164. AttestationType: cred.AttestationType,
  165. AAGUID: cred.Authenticator.AAGUID,
  166. SignCount: cred.Authenticator.SignCount,
  167. CloneWarning: false,
  168. }
  169. if err := db.Insert(ctx, c); err != nil {
  170. return nil, err
  171. }
  172. return c, nil
  173. }
  174. // DeleteCredential will delete WebAuthnCredential
  175. func DeleteCredential(id, userID int64) (bool, error) {
  176. return deleteCredential(db.DefaultContext, id, userID)
  177. }
  178. func deleteCredential(ctx context.Context, id, userID int64) (bool, error) {
  179. had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{})
  180. return had > 0, err
  181. }
  182. // WebAuthnCredentials implementns the webauthn.User interface
  183. func WebAuthnCredentials(userID int64) ([]webauthn.Credential, error) {
  184. dbCreds, err := GetWebAuthnCredentialsByUID(userID)
  185. if err != nil {
  186. return nil, err
  187. }
  188. return dbCreds.ToCredentials(), nil
  189. }