package barcode import ( "crypto/rand" "time" "github.com/ory/fosite" "github.com/ory/x/errorsx" "golang.org/x/crypto/bcrypt" "edge-infra.dev/pkg/edge/iam/config" iamErrors "edge-infra.dev/pkg/edge/iam/errors" ) type OpaqueStrategy struct { } func (s *OpaqueStrategy) GetKey(barcode string) string { barcodePrefixLen := uint8(len(config.BarcodePrefix())) /* #nosec G115 value from ENV and ENV is considered trusted */ key := barcode[len(config.BarcodePrefix()) : barcodePrefixLen+config.GetBarcodeKeyLength()] return key } func getSecret(barcode string) string { barcodePrefixLen := uint8(len(config.BarcodePrefix())) /* #nosec G115 value from ENV and ENV is considered trusted */ secret := barcode[barcodePrefixLen+config.GetBarcodeKeyLength():] return secret } func (s *OpaqueStrategy) Verify(barcode string, barcodeData *Barcode) error { err := compareHashAndSecret(barcode, barcodeData) if err != nil { return err } return isExpired(barcodeData) } func (s *OpaqueStrategy) Generate() (string, error) { return barcode128A() } func isExpired(barcodeData *Barcode) error { expires := barcodeExpiresAt(barcodeData) if expires.Before(time.Now().UTC()) { return iamErrors.ErrExpiredBarcode } return nil } func barcodeExpiresAt(barcodeData *Barcode) time.Time { return barcodeData.CreatedAt.UTC(). Add(config.GetBarcodeLifeSpan()). Round(time.Second) } func accessExpiresAt() time.Time { return time.Now().UTC(). Add(config.AccessTokenLifespan()). Round(time.Second) } func refreshExpiresAt() time.Time { return time.Now().UTC(). Add(config.RefreshTokenLifespan()). Round(time.Second) } func compareHashAndSecret(barcode string, barcodeData *Barcode) error { hash := barcodeData.Secret secret := getSecret(barcode) if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(secret)); err != nil { hint := "Cannot accept barcode, try signing in another way." return errorsx.WithStack(fosite.ErrAccessDenied. WithHint(hint). WithDebug(err.Error())) } return nil } // generate a random string with length and charset limitations of barcode 128 A. func barcode128A() (string, error) { barcodePrefixLen := uint8(len(config.BarcodePrefix())) /* #nosec G115 value from ENV and ENV is considered trusted */ length := config.GetBarcodeLength() - barcodePrefixLen charset := config.GetBarcodeCharset() // read cryptographically secure pseudorandom numbers and write them to bytes bytes := make([]byte, length) _, err := rand.Read(bytes) if err != nil { return "", err } // map characters in our charset due to the limitations of barcode. for i, v := range bytes { bytes[i] = charset[v%byte(len(charset))] } return config.BarcodePrefix() + string(bytes), nil }