Skip to content

Commit af5c4d0

Browse files
authored
feat: implement SHA-512 algorithm to ProtectSheet (qax-os#1115)
1 parent 9e64df6 commit af5c4d0

10 files changed

+223
-54
lines changed

chart.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -980,12 +980,12 @@ func (f *File) getFormatChart(format string, combo []string) (*formatChart, []*f
980980
return formatSet, comboCharts, err
981981
}
982982
if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok {
983-
return formatSet, comboCharts, newUnsupportChartType(comboChart.Type)
983+
return formatSet, comboCharts, newUnsupportedChartType(comboChart.Type)
984984
}
985985
comboCharts = append(comboCharts, comboChart)
986986
}
987987
if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok {
988-
return formatSet, comboCharts, newUnsupportChartType(formatSet.Type)
988+
return formatSet, comboCharts, newUnsupportedChartType(formatSet.Type)
989989
}
990990
return formatSet, comboCharts, err
991991
}

crypt.go

+55-9
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var (
4343
packageOffset = 8 // First 8 bytes are the size of the stream
4444
packageEncryptionChunkSize = 4096
4545
iterCount = 50000
46+
sheetProtectionSpinCount = 1e5
4647
oleIdentifier = []byte{
4748
0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,
4849
}
@@ -146,7 +147,7 @@ func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
146147
case "standard":
147148
return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt)
148149
default:
149-
err = ErrUnsupportEncryptMechanism
150+
err = ErrUnsupportedEncryptMechanism
150151
}
151152
return
152153
}
@@ -307,7 +308,7 @@ func encryptionMechanism(buffer []byte) (mechanism string, err error) {
307308
} else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 {
308309
mechanism = "extensible"
309310
}
310-
err = ErrUnsupportEncryptMechanism
311+
err = ErrUnsupportedEncryptMechanism
311312
return
312313
}
313314

@@ -387,14 +388,14 @@ func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier Standa
387388
key = hashing("sha1", iterator, key)
388389
}
389390
var block int
390-
hfinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
391+
hFinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
391392
cbRequiredKeyLength := int(header.KeySize) / 8
392393
cbHash := sha1.Size
393394
buf1 := bytes.Repeat([]byte{0x36}, 64)
394-
buf1 = append(standardXORBytes(hfinal, buf1[:cbHash]), buf1[cbHash:]...)
395+
buf1 = append(standardXORBytes(hFinal, buf1[:cbHash]), buf1[cbHash:]...)
395396
x1 := hashing("sha1", buf1)
396397
buf2 := bytes.Repeat([]byte{0x5c}, 64)
397-
buf2 = append(standardXORBytes(hfinal, buf2[:cbHash]), buf2[cbHash:]...)
398+
buf2 = append(standardXORBytes(hFinal, buf2[:cbHash]), buf2[cbHash:]...)
398399
x2 := hashing("sha1", buf2)
399400
x3 := append(x1, x2...)
400401
keyDerived := x3[:cbRequiredKeyLength]
@@ -417,7 +418,8 @@ func standardXORBytes(a, b []byte) []byte {
417418
// ECMA-376 Agile Encryption
418419

419420
// agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption.
420-
// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256, SHA384 and SHA512.
421+
// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256,
422+
// SHA384 and SHA512.
421423
func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (packageBuf []byte, err error) {
422424
var encryptionInfo Encryption
423425
if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil {
@@ -605,11 +607,55 @@ func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
605607
return iv, nil
606608
}
607609

608-
// randomBytes returns securely generated random bytes. It will return an error if the system's
609-
// secure random number generator fails to function correctly, in which case the caller should not
610-
// continue.
610+
// randomBytes returns securely generated random bytes. It will return an
611+
// error if the system's secure random number generator fails to function
612+
// correctly, in which case the caller should not continue.
611613
func randomBytes(n int) ([]byte, error) {
612614
b := make([]byte, n)
613615
_, err := rand.Read(b)
614616
return b, err
615617
}
618+
619+
// ISO Write Protection Method
620+
621+
// genISOPasswdHash implements the ISO password hashing algorithm by given
622+
// plaintext password, name of the cryptographic hash algorithm, salt value
623+
// and spin count.
624+
func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashValue, saltValue string, err error) {
625+
if len(passwd) < 1 || len(passwd) > MaxFieldLength {
626+
err = ErrPasswordLengthInvalid
627+
return
628+
}
629+
hash, ok := map[string]string{
630+
"MD4": "md4",
631+
"MD5": "md5",
632+
"SHA-1": "sha1",
633+
"SHA-256": "sha256",
634+
"SHA-384": "sha384",
635+
"SHA-512": "sha512",
636+
}[hashAlgorithm]
637+
if !ok {
638+
err = ErrUnsupportedHashAlgorithm
639+
return
640+
}
641+
var b bytes.Buffer
642+
s, _ := randomBytes(16)
643+
if salt != "" {
644+
if s, err = base64.StdEncoding.DecodeString(salt); err != nil {
645+
return
646+
}
647+
}
648+
b.Write(s)
649+
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
650+
passwordBuffer, _ := encoder.Bytes([]byte(passwd))
651+
b.Write(passwordBuffer)
652+
// Generate the initial hash.
653+
key := hashing(hash, b.Bytes())
654+
// Now regenerate until spin count.
655+
for i := 0; i < spinCount; i++ {
656+
iterator := createUInt32LEBuffer(i, 4)
657+
key = hashing(hash, key, iterator)
658+
}
659+
hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s)
660+
return
661+
}

crypt_test.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,27 @@ func TestEncrypt(t *testing.T) {
2828
func TestEncryptionMechanism(t *testing.T) {
2929
mechanism, err := encryptionMechanism([]byte{3, 0, 3, 0})
3030
assert.Equal(t, mechanism, "extensible")
31-
assert.EqualError(t, err, ErrUnsupportEncryptMechanism.Error())
31+
assert.EqualError(t, err, ErrUnsupportedEncryptMechanism.Error())
3232
_, err = encryptionMechanism([]byte{})
3333
assert.EqualError(t, err, ErrUnknownEncryptMechanism.Error())
3434
}
3535

3636
func TestHashing(t *testing.T) {
37-
assert.Equal(t, hashing("unsupportHashAlgorithm", []byte{}), []uint8([]byte(nil)))
37+
assert.Equal(t, hashing("unsupportedHashAlgorithm", []byte{}), []uint8([]byte(nil)))
38+
}
39+
40+
func TestGenISOPasswdHash(t *testing.T) {
41+
for hashAlgorithm, expected := range map[string][]string{
42+
"MD4": {"2lZQZUubVHLm/t6KsuHX4w==", "TTHjJdU70B/6Zq83XGhHVA=="},
43+
"MD5": {"HWbqyd4dKKCjk1fEhk2kuQ==", "8ADyorkumWCayIukRhlVKQ=="},
44+
"SHA-1": {"XErQIV3Ol+nhXkyCxrLTEQm+mSc=", "I3nDtyf59ASaNX1l6KpFnA=="},
45+
"SHA-256": {"7oqMFyfED+mPrzRIBQ+KpKT4SClMHEPOZldliP15xAA=", "ru1R/w3P3Jna2Qo+EE8QiA=="},
46+
"SHA-384": {"nMODLlxsC8vr0btcq0kp/jksg5FaI3az5Sjo1yZk+/x4bFzsuIvpDKUhJGAk/fzo", "Zjq9/jHlgOY6MzFDSlVNZg=="},
47+
"SHA-512": {"YZ6jrGOFQgVKK3rDK/0SHGGgxEmFJglQIIRamZc2PkxVtUBp54fQn96+jVXEOqo6dtCSanqksXGcm/h3KaiR4Q==", "p5s/bybHBPtusI7EydTIrg=="},
48+
} {
49+
hashValue, saltValue, err := genISOPasswdHash("password", hashAlgorithm, expected[1], int(sheetProtectionSpinCount))
50+
assert.NoError(t, err)
51+
assert.Equal(t, expected[0], hashValue)
52+
assert.Equal(t, expected[1], saltValue)
53+
}
3854
}

datavalidation.go

+13-13
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const (
2929
DataValidationTypeDate
3030
DataValidationTypeDecimal
3131
typeList // inline use
32-
DataValidationTypeTextLeng
32+
DataValidationTypeTextLength
3333
DataValidationTypeTime
3434
// DataValidationTypeWhole Integer
3535
DataValidationTypeWhole
@@ -116,7 +116,7 @@ func (dd *DataValidation) SetInput(title, msg string) {
116116
func (dd *DataValidation) SetDropList(keys []string) error {
117117
formula := strings.Join(keys, ",")
118118
if MaxFieldLength < len(utf16.Encode([]rune(formula))) {
119-
return ErrDataValidationFormulaLenth
119+
return ErrDataValidationFormulaLength
120120
}
121121
dd.Formula1 = fmt.Sprintf(`<formula1>"%s"</formula1>`, formulaEscaper.Replace(formula))
122122
dd.Type = convDataValidationType(typeList)
@@ -155,7 +155,7 @@ func (dd *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o D
155155
}
156156
dd.Formula1, dd.Formula2 = formula1, formula2
157157
dd.Type = convDataValidationType(t)
158-
dd.Operator = convDataValidationOperatior(o)
158+
dd.Operator = convDataValidationOperator(o)
159159
return nil
160160
}
161161

@@ -192,22 +192,22 @@ func (dd *DataValidation) SetSqref(sqref string) {
192192
// convDataValidationType get excel data validation type.
193193
func convDataValidationType(t DataValidationType) string {
194194
typeMap := map[DataValidationType]string{
195-
typeNone: "none",
196-
DataValidationTypeCustom: "custom",
197-
DataValidationTypeDate: "date",
198-
DataValidationTypeDecimal: "decimal",
199-
typeList: "list",
200-
DataValidationTypeTextLeng: "textLength",
201-
DataValidationTypeTime: "time",
202-
DataValidationTypeWhole: "whole",
195+
typeNone: "none",
196+
DataValidationTypeCustom: "custom",
197+
DataValidationTypeDate: "date",
198+
DataValidationTypeDecimal: "decimal",
199+
typeList: "list",
200+
DataValidationTypeTextLength: "textLength",
201+
DataValidationTypeTime: "time",
202+
DataValidationTypeWhole: "whole",
203203
}
204204

205205
return typeMap[t]
206206

207207
}
208208

209-
// convDataValidationOperatior get excel data validation operator.
210-
func convDataValidationOperatior(o DataValidationOperator) string {
209+
// convDataValidationOperator get excel data validation operator.
210+
func convDataValidationOperator(o DataValidationOperator) string {
211211
typeMap := map[DataValidationOperator]string{
212212
DataValidationOperatorBetween: "between",
213213
DataValidationOperatorEqual: "equal",

datavalidation_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func TestDataValidationError(t *testing.T) {
9494
t.Errorf("data validation error. Formula1 must be empty!")
9595
return
9696
}
97-
assert.EqualError(t, err, ErrDataValidationFormulaLenth.Error())
97+
assert.EqualError(t, err, ErrDataValidationFormulaLength.Error())
9898
assert.EqualError(t, dvRange.SetRange(nil, 20, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error())
9999
assert.EqualError(t, dvRange.SetRange(10, nil, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error())
100100
assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan))
@@ -114,7 +114,7 @@ func TestDataValidationError(t *testing.T) {
114114
err = dvRange.SetDropList(keys)
115115
assert.Equal(t, prevFormula1, dvRange.Formula1,
116116
"Formula1 should be unchanged for invalid input %v", keys)
117-
assert.EqualError(t, err, ErrDataValidationFormulaLenth.Error())
117+
assert.EqualError(t, err, ErrDataValidationFormulaLength.Error())
118118
}
119119
assert.NoError(t, f.AddDataValidation("Sheet1", dvRange))
120120
assert.NoError(t, dvRange.SetRange(

errors.go

+35-15
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,50 @@ import (
1616
"fmt"
1717
)
1818

19-
// newInvalidColumnNameError defined the error message on receiving the invalid column name.
19+
// newInvalidColumnNameError defined the error message on receiving the
20+
// invalid column name.
2021
func newInvalidColumnNameError(col string) error {
2122
return fmt.Errorf("invalid column name %q", col)
2223
}
2324

24-
// newInvalidRowNumberError defined the error message on receiving the invalid row number.
25+
// newInvalidRowNumberError defined the error message on receiving the invalid
26+
// row number.
2527
func newInvalidRowNumberError(row int) error {
2628
return fmt.Errorf("invalid row number %d", row)
2729
}
2830

29-
// newInvalidCellNameError defined the error message on receiving the invalid cell name.
31+
// newInvalidCellNameError defined the error message on receiving the invalid
32+
// cell name.
3033
func newInvalidCellNameError(cell string) error {
3134
return fmt.Errorf("invalid cell name %q", cell)
3235
}
3336

34-
// newInvalidExcelDateError defined the error message on receiving the data with negative values.
37+
// newInvalidExcelDateError defined the error message on receiving the data
38+
// with negative values.
3539
func newInvalidExcelDateError(dateValue float64) error {
3640
return fmt.Errorf("invalid date value %f, negative values are not supported", dateValue)
3741
}
3842

39-
// newUnsupportChartType defined the error message on receiving the chart type are unsupported.
40-
func newUnsupportChartType(chartType string) error {
43+
// newUnsupportedChartType defined the error message on receiving the chart
44+
// type are unsupported.
45+
func newUnsupportedChartType(chartType string) error {
4146
return fmt.Errorf("unsupported chart type %s", chartType)
4247
}
4348

44-
// newUnzipSizeLimitError defined the error message on unzip size exceeds the limit.
49+
// newUnzipSizeLimitError defined the error message on unzip size exceeds the
50+
// limit.
4551
func newUnzipSizeLimitError(unzipSizeLimit int64) error {
4652
return fmt.Errorf("unzip size exceeds the %d bytes limit", unzipSizeLimit)
4753
}
4854

49-
// newInvalidStyleID defined the error message on receiving the invalid style ID.
55+
// newInvalidStyleID defined the error message on receiving the invalid style
56+
// ID.
5057
func newInvalidStyleID(styleID int) error {
5158
return fmt.Errorf("invalid style ID %d, negative values are not supported", styleID)
5259
}
5360

54-
// newFieldLengthError defined the error message on receiving the field length overflow.
61+
// newFieldLengthError defined the error message on receiving the field length
62+
// overflow.
5563
func newFieldLengthError(name string) error {
5664
return fmt.Errorf("field %s must be less or equal than 255 characters", name)
5765
}
@@ -103,12 +111,18 @@ var (
103111
ErrMaxFileNameLength = errors.New("file name length exceeds maximum limit")
104112
// ErrEncrypt defined the error message on encryption spreadsheet.
105113
ErrEncrypt = errors.New("not support encryption currently")
106-
// ErrUnknownEncryptMechanism defined the error message on unsupport
114+
// ErrUnknownEncryptMechanism defined the error message on unsupported
107115
// encryption mechanism.
108116
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
109-
// ErrUnsupportEncryptMechanism defined the error message on unsupport
117+
// ErrUnsupportedEncryptMechanism defined the error message on unsupported
110118
// encryption mechanism.
111-
ErrUnsupportEncryptMechanism = errors.New("unsupport encryption mechanism")
119+
ErrUnsupportedEncryptMechanism = errors.New("unsupported encryption mechanism")
120+
// ErrUnsupportedHashAlgorithm defined the error message on unsupported
121+
// hash algorithm.
122+
ErrUnsupportedHashAlgorithm = errors.New("unsupported hash algorithm")
123+
// ErrPasswordLengthInvalid defined the error message on invalid password
124+
// length.
125+
ErrPasswordLengthInvalid = errors.New("password length invalid")
112126
// ErrParameterRequired defined the error message on receive the empty
113127
// parameter.
114128
ErrParameterRequired = errors.New("parameter is required")
@@ -131,11 +145,17 @@ var (
131145
// ErrSheetIdx defined the error message on receive the invalid worksheet
132146
// index.
133147
ErrSheetIdx = errors.New("invalid worksheet index")
148+
// ErrUnprotectSheet defined the error message on worksheet has set no
149+
// protection.
150+
ErrUnprotectSheet = errors.New("worksheet has set no protect")
151+
// ErrUnprotectSheetPassword defined the error message on remove sheet
152+
// protection with password verification failed.
153+
ErrUnprotectSheetPassword = errors.New("worksheet protect password not match")
134154
// ErrGroupSheets defined the error message on group sheets.
135155
ErrGroupSheets = errors.New("group worksheet must contain an active worksheet")
136-
// ErrDataValidationFormulaLenth defined the error message for receiving a
156+
// ErrDataValidationFormulaLength defined the error message for receiving a
137157
// data validation formula length that exceeds the limit.
138-
ErrDataValidationFormulaLenth = errors.New("data validation must be 0-255 characters")
158+
ErrDataValidationFormulaLength = errors.New("data validation must be 0-255 characters")
139159
// ErrDataValidationRange defined the error message on set decimal range
140160
// exceeds limit.
141161
ErrDataValidationRange = errors.New("data validation range exceeds limit")
@@ -164,5 +184,5 @@ var (
164184
ErrSparkline = errors.New("must have the same number of 'Location' and 'Range' parameters")
165185
// ErrSparklineStyle defined the error message on receive the invalid
166186
// sparkline Style parameters.
167-
ErrSparklineStyle = errors.New("parameter 'Style' must betweent 0-35")
187+
ErrSparklineStyle = errors.New("parameter 'Style' must between 0-35")
168188
)

0 commit comments

Comments
 (0)