mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	* Initial commit for 2FA support Signed-off-by: Andrew <write@imaginarycode.com> * Add vendored files * Add missing depends * A few clean ups * Added improvements, proper encryption * Better encryption key * Simplify "key" generation * Make 2FA enrollment page more robust * Fix typo * Rename twofa/2FA to TwoFactor * UNIQUE INDEX -> UNIQUE
		
			
				
	
	
		
			417 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			417 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package qr can be used to create QR barcodes.
 | |
| package qr
 | |
| 
 | |
| import (
 | |
| 	"image"
 | |
| 
 | |
| 	"github.com/boombuler/barcode"
 | |
| 	"github.com/boombuler/barcode/utils"
 | |
| )
 | |
| 
 | |
| type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error)
 | |
| 
 | |
| // Encoding mode for QR Codes.
 | |
| type Encoding byte
 | |
| 
 | |
| const (
 | |
| 	// Auto will choose ths best matching encoding
 | |
| 	Auto Encoding = iota
 | |
| 	// Numeric encoding only encodes numbers [0-9]
 | |
| 	Numeric
 | |
| 	// AlphaNumeric encoding only encodes uppercase letters, numbers and  [Space], $, %, *, +, -, ., /, :
 | |
| 	AlphaNumeric
 | |
| 	// Unicode encoding encodes the string as utf-8
 | |
| 	Unicode
 | |
| 	// only for testing purpose
 | |
| 	unknownEncoding
 | |
| )
 | |
| 
 | |
| func (e Encoding) getEncoder() encodeFn {
 | |
| 	switch e {
 | |
| 	case Auto:
 | |
| 		return encodeAuto
 | |
| 	case Numeric:
 | |
| 		return encodeNumeric
 | |
| 	case AlphaNumeric:
 | |
| 		return encodeAlphaNumeric
 | |
| 	case Unicode:
 | |
| 		return encodeUnicode
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (e Encoding) String() string {
 | |
| 	switch e {
 | |
| 	case Auto:
 | |
| 		return "Auto"
 | |
| 	case Numeric:
 | |
| 		return "Numeric"
 | |
| 	case AlphaNumeric:
 | |
| 		return "AlphaNumeric"
 | |
| 	case Unicode:
 | |
| 		return "Unicode"
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // Encode returns a QR barcode with the given content, error correction level and uses the given encoding
 | |
| func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) {
 | |
| 	bits, vi, err := mode.getEncoder()(content, level)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	blocks := splitToBlocks(bits.IterateBytes(), vi)
 | |
| 	data := blocks.interleave(vi)
 | |
| 	result := render(data, vi)
 | |
| 	result.content = content
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| func render(data []byte, vi *versionInfo) *qrcode {
 | |
| 	dim := vi.modulWidth()
 | |
| 	results := make([]*qrcode, 8)
 | |
| 	for i := 0; i < 8; i++ {
 | |
| 		results[i] = newBarcode(dim)
 | |
| 	}
 | |
| 
 | |
| 	occupied := newBarcode(dim)
 | |
| 
 | |
| 	setAll := func(x int, y int, val bool) {
 | |
| 		occupied.Set(x, y, true)
 | |
| 		for i := 0; i < 8; i++ {
 | |
| 			results[i].Set(x, y, val)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	drawFinderPatterns(vi, setAll)
 | |
| 	drawAlignmentPatterns(occupied, vi, setAll)
 | |
| 
 | |
| 	//Timing Pattern:
 | |
| 	var i int
 | |
| 	for i = 0; i < dim; i++ {
 | |
| 		if !occupied.Get(i, 6) {
 | |
| 			setAll(i, 6, i%2 == 0)
 | |
| 		}
 | |
| 		if !occupied.Get(6, i) {
 | |
| 			setAll(6, i, i%2 == 0)
 | |
| 		}
 | |
| 	}
 | |
| 	// Dark Module
 | |
| 	setAll(8, dim-8, true)
 | |
| 
 | |
| 	drawVersionInfo(vi, setAll)
 | |
| 	drawFormatInfo(vi, -1, occupied.Set)
 | |
| 	for i := 0; i < 8; i++ {
 | |
| 		drawFormatInfo(vi, i, results[i].Set)
 | |
| 	}
 | |
| 
 | |
| 	// Write the data
 | |
| 	var curBitNo int
 | |
| 
 | |
| 	for pos := range iterateModules(occupied) {
 | |
| 		var curBit bool
 | |
| 		if curBitNo < len(data)*8 {
 | |
| 			curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1
 | |
| 		} else {
 | |
| 			curBit = false
 | |
| 		}
 | |
| 
 | |
| 		for i := 0; i < 8; i++ {
 | |
| 			setMasked(pos.X, pos.Y, curBit, i, results[i].Set)
 | |
| 		}
 | |
| 		curBitNo++
 | |
| 	}
 | |
| 
 | |
| 	lowestPenalty := ^uint(0)
 | |
| 	lowestPenaltyIdx := -1
 | |
| 	for i := 0; i < 8; i++ {
 | |
| 		p := results[i].calcPenalty()
 | |
| 		if p < lowestPenalty {
 | |
| 			lowestPenalty = p
 | |
| 			lowestPenaltyIdx = i
 | |
| 		}
 | |
| 	}
 | |
| 	return results[lowestPenaltyIdx]
 | |
| }
 | |
| 
 | |
| func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) {
 | |
| 	switch mask {
 | |
| 	case 0:
 | |
| 		val = val != (((y + x) % 2) == 0)
 | |
| 		break
 | |
| 	case 1:
 | |
| 		val = val != ((y % 2) == 0)
 | |
| 		break
 | |
| 	case 2:
 | |
| 		val = val != ((x % 3) == 0)
 | |
| 		break
 | |
| 	case 3:
 | |
| 		val = val != (((y + x) % 3) == 0)
 | |
| 		break
 | |
| 	case 4:
 | |
| 		val = val != (((y/2 + x/3) % 2) == 0)
 | |
| 		break
 | |
| 	case 5:
 | |
| 		val = val != (((y*x)%2)+((y*x)%3) == 0)
 | |
| 		break
 | |
| 	case 6:
 | |
| 		val = val != ((((y*x)%2)+((y*x)%3))%2 == 0)
 | |
| 		break
 | |
| 	case 7:
 | |
| 		val = val != ((((y+x)%2)+((y*x)%3))%2 == 0)
 | |
| 	}
 | |
| 	set(x, y, val)
 | |
| }
 | |
| 
 | |
| func iterateModules(occupied *qrcode) <-chan image.Point {
 | |
| 	result := make(chan image.Point)
 | |
| 	allPoints := make(chan image.Point)
 | |
| 	go func() {
 | |
| 		curX := occupied.dimension - 1
 | |
| 		curY := occupied.dimension - 1
 | |
| 		isUpward := true
 | |
| 
 | |
| 		for true {
 | |
| 			if isUpward {
 | |
| 				allPoints <- image.Pt(curX, curY)
 | |
| 				allPoints <- image.Pt(curX-1, curY)
 | |
| 				curY--
 | |
| 				if curY < 0 {
 | |
| 					curY = 0
 | |
| 					curX -= 2
 | |
| 					if curX == 6 {
 | |
| 						curX--
 | |
| 					}
 | |
| 					if curX < 0 {
 | |
| 						break
 | |
| 					}
 | |
| 					isUpward = false
 | |
| 				}
 | |
| 			} else {
 | |
| 				allPoints <- image.Pt(curX, curY)
 | |
| 				allPoints <- image.Pt(curX-1, curY)
 | |
| 				curY++
 | |
| 				if curY >= occupied.dimension {
 | |
| 					curY = occupied.dimension - 1
 | |
| 					curX -= 2
 | |
| 					if curX == 6 {
 | |
| 						curX--
 | |
| 					}
 | |
| 					isUpward = true
 | |
| 					if curX < 0 {
 | |
| 						break
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		close(allPoints)
 | |
| 	}()
 | |
| 	go func() {
 | |
| 		for pt := range allPoints {
 | |
| 			if !occupied.Get(pt.X, pt.Y) {
 | |
| 				result <- pt
 | |
| 			}
 | |
| 		}
 | |
| 		close(result)
 | |
| 	}()
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) {
 | |
| 	dim := vi.modulWidth()
 | |
| 	drawPattern := func(xoff int, yoff int) {
 | |
| 		for x := -1; x < 8; x++ {
 | |
| 			for y := -1; y < 8; y++ {
 | |
| 				val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0)
 | |
| 
 | |
| 				if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim {
 | |
| 					set(x+xoff, y+yoff, val)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	drawPattern(0, 0)
 | |
| 	drawPattern(0, dim-7)
 | |
| 	drawPattern(dim-7, 0)
 | |
| }
 | |
| 
 | |
| func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) {
 | |
| 	drawPattern := func(xoff int, yoff int) {
 | |
| 		for x := -2; x <= 2; x++ {
 | |
| 			for y := -2; y <= 2; y++ {
 | |
| 				val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0)
 | |
| 				set(x+xoff, y+yoff, val)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	positions := vi.alignmentPatternPlacements()
 | |
| 
 | |
| 	for _, x := range positions {
 | |
| 		for _, y := range positions {
 | |
| 			if occupied.Get(x, y) {
 | |
| 				continue
 | |
| 			}
 | |
| 			drawPattern(x, y)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{
 | |
| 	L: {
 | |
| 		0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false},
 | |
| 		1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true},
 | |
| 		2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false},
 | |
| 		3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true},
 | |
| 		4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true},
 | |
| 		5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false},
 | |
| 		6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true},
 | |
| 		7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false},
 | |
| 	},
 | |
| 	M: {
 | |
| 		0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false},
 | |
| 		1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true},
 | |
| 		2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false},
 | |
| 		3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true},
 | |
| 		4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true},
 | |
| 		5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false},
 | |
| 		6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true},
 | |
| 		7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false},
 | |
| 	},
 | |
| 	Q: {
 | |
| 		0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true},
 | |
| 		1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false},
 | |
| 		2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true},
 | |
| 		3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false},
 | |
| 		4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false},
 | |
| 		5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true},
 | |
| 		6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false},
 | |
| 		7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true},
 | |
| 	},
 | |
| 	H: {
 | |
| 		0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true},
 | |
| 		1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false},
 | |
| 		2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true},
 | |
| 		3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false},
 | |
| 		4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
 | |
| 		5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true},
 | |
| 		6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false},
 | |
| 		7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true},
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) {
 | |
| 	var formatInfo []bool
 | |
| 
 | |
| 	if usedMask == -1 {
 | |
| 		formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask.
 | |
| 	} else {
 | |
| 		formatInfo = formatInfos[vi.Level][usedMask]
 | |
| 	}
 | |
| 
 | |
| 	if len(formatInfo) == 15 {
 | |
| 		dim := vi.modulWidth()
 | |
| 		set(0, 8, formatInfo[0])
 | |
| 		set(1, 8, formatInfo[1])
 | |
| 		set(2, 8, formatInfo[2])
 | |
| 		set(3, 8, formatInfo[3])
 | |
| 		set(4, 8, formatInfo[4])
 | |
| 		set(5, 8, formatInfo[5])
 | |
| 		set(7, 8, formatInfo[6])
 | |
| 		set(8, 8, formatInfo[7])
 | |
| 		set(8, 7, formatInfo[8])
 | |
| 		set(8, 5, formatInfo[9])
 | |
| 		set(8, 4, formatInfo[10])
 | |
| 		set(8, 3, formatInfo[11])
 | |
| 		set(8, 2, formatInfo[12])
 | |
| 		set(8, 1, formatInfo[13])
 | |
| 		set(8, 0, formatInfo[14])
 | |
| 
 | |
| 		set(8, dim-1, formatInfo[0])
 | |
| 		set(8, dim-2, formatInfo[1])
 | |
| 		set(8, dim-3, formatInfo[2])
 | |
| 		set(8, dim-4, formatInfo[3])
 | |
| 		set(8, dim-5, formatInfo[4])
 | |
| 		set(8, dim-6, formatInfo[5])
 | |
| 		set(8, dim-7, formatInfo[6])
 | |
| 		set(dim-8, 8, formatInfo[7])
 | |
| 		set(dim-7, 8, formatInfo[8])
 | |
| 		set(dim-6, 8, formatInfo[9])
 | |
| 		set(dim-5, 8, formatInfo[10])
 | |
| 		set(dim-4, 8, formatInfo[11])
 | |
| 		set(dim-3, 8, formatInfo[12])
 | |
| 		set(dim-2, 8, formatInfo[13])
 | |
| 		set(dim-1, 8, formatInfo[14])
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var versionInfoBitsByVersion = map[byte][]bool{
 | |
| 	7:  []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false},
 | |
| 	8:  []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false},
 | |
| 	9:  []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true},
 | |
| 	10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true},
 | |
| 	11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false},
 | |
| 	12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
 | |
| 	13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true},
 | |
| 	14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true},
 | |
| 	15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false},
 | |
| 	16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false},
 | |
| 	17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true},
 | |
| 	18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true},
 | |
| 	19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false},
 | |
| 	20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false},
 | |
| 	21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true},
 | |
| 	22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true},
 | |
| 	23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false},
 | |
| 	24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false},
 | |
| 	25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true},
 | |
| 	26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true},
 | |
| 	27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false},
 | |
| 	28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false},
 | |
| 	29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true},
 | |
| 	30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true},
 | |
| 	31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false},
 | |
| 	32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true},
 | |
| 	33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false},
 | |
| 	34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false},
 | |
| 	35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true},
 | |
| 	36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true},
 | |
| 	37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false},
 | |
| 	38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false},
 | |
| 	39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true},
 | |
| 	40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true},
 | |
| }
 | |
| 
 | |
| func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) {
 | |
| 	versionInfoBits, ok := versionInfoBitsByVersion[vi.Version]
 | |
| 
 | |
| 	if ok && len(versionInfoBits) > 0 {
 | |
| 		for i := 0; i < len(versionInfoBits); i++ {
 | |
| 			x := (vi.modulWidth() - 11) + i%3
 | |
| 			y := i / 3
 | |
| 			set(x, y, versionInfoBits[len(versionInfoBits)-i-1])
 | |
| 			set(y, x, versionInfoBits[len(versionInfoBits)-i-1])
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) {
 | |
| 	for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ {
 | |
| 		bl.AddBit(false)
 | |
| 	}
 | |
| 
 | |
| 	for bl.Len()%8 != 0 {
 | |
| 		bl.AddBit(false)
 | |
| 	}
 | |
| 
 | |
| 	for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ {
 | |
| 		if i%2 == 0 {
 | |
| 			bl.AddByte(236)
 | |
| 		} else {
 | |
| 			bl.AddByte(17)
 | |
| 		}
 | |
| 	}
 | |
| }
 |