cubefs
244 строки · 6.2 Кб
1// Copyright 2018 The CubeFS Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12// implied. See the License for the specific language governing
13// permissions and limitations under the License.
14
15package cryptoutil
16
17import (
18"bytes"
19"crypto/aes"
20"crypto/cipher"
21"crypto/hmac"
22"crypto/md5"
23"crypto/rand"
24"crypto/sha256"
25"crypto/tls"
26"crypto/x509"
27"encoding/base64"
28"encoding/binary"
29"fmt"
30"io"
31rand2 "math/rand"
32"net/http"
33"strconv"
34"time"
35"unsafe"
36)
37
38func pad(src []byte) []byte {
39padding := aes.BlockSize - len(src)%aes.BlockSize
40padtext := bytes.Repeat([]byte{byte(padding)}, padding)
41return append(src, padtext...)
42}
43
44func unpad(src []byte) []byte {
45length := len(src)
46unpadding := int(src[length-1])
47return src[:(length - unpadding)]
48}
49
50// AesEncryptCBC defines aes encryption with CBC
51func AesEncryptCBC(key, plaintext []byte) (ciphertext []byte, err error) {
52var block cipher.Block
53
54if len(plaintext) == 0 {
55err = fmt.Errorf("input for encryption is invalid")
56return
57}
58
59paddedText := pad(plaintext)
60
61if len(paddedText)%aes.BlockSize != 0 {
62err = fmt.Errorf("paddedText [len=%d] is not a multiple of the block size", len(paddedText))
63return
64}
65
66block, err = aes.NewCipher(key)
67if err != nil {
68return
69}
70
71ciphertext = make([]byte, aes.BlockSize+len(paddedText))
72iv := ciphertext[:aes.BlockSize]
73if _, err = io.ReadFull(rand.Reader, iv); err != nil {
74return
75}
76
77cbc := cipher.NewCBCEncrypter(block, iv)
78cbc.CryptBlocks(ciphertext[aes.BlockSize:], paddedText)
79
80return
81}
82
83// AesDecryptCBC defines aes decryption with CBC
84func AesDecryptCBC(key, ciphertext []byte) (plaintext []byte, err error) {
85var block cipher.Block
86
87if block, err = aes.NewCipher(key); err != nil {
88return
89}
90
91if len(ciphertext) < aes.BlockSize {
92err = fmt.Errorf("ciphertext [len=%d] too short; should greater than blocksize", len(ciphertext))
93return
94}
95
96iv := ciphertext[:aes.BlockSize]
97ciphertext = ciphertext[aes.BlockSize:]
98
99cbc := cipher.NewCBCDecrypter(block, iv)
100cbc.CryptBlocks(ciphertext, ciphertext)
101
102plaintext = unpad(ciphertext)
103
104return
105}
106
107// GenSecretKey generate a secret key according to pair {ts, id}
108func GenSecretKey(key []byte, ts int64, id string) (secretKey []byte) {
109b := make([]byte, 8)
110binary.LittleEndian.PutUint64(b, uint64(ts))
111data := append(b, []byte(id)...)
112secretKey = genKey(key, data)
113return
114}
115
116func genKey(key []byte, data []byte) (sessionKey []byte) {
117h := hmac.New(sha256.New, []byte(key))
118h.Write([]byte(data))
119sessionKey = h.Sum(nil)
120return
121}
122
123// AuthGenSessionKeyTS authnode generates a session key according to its master key and current timestamp
124func AuthGenSessionKeyTS(key []byte) (sessionKey []byte) {
125data := []byte(strconv.FormatInt(int64(time.Now().Unix()), 10))
126sessionKey = genKey(key, data)
127return
128}
129
130// Base64Encode encoding using base64
131func Base64Encode(text []byte) (encodedText string) {
132encodedText = base64.StdEncoding.EncodeToString(text)
133return
134}
135
136// Base64Decode Decoding using base64
137func Base64Decode(encodedText string) (text []byte, err error) {
138text, err = base64.StdEncoding.DecodeString(encodedText)
139return
140}
141
142// EncodeMessage encode a message with aes encrption, md5 signature
143func EncodeMessage(plaintext []byte, key []byte) (message string, err error) {
144var cipher []byte
145
146if len(plaintext) > MaxAllocSize {
147return "too max packet", fmt.Errorf("too max packet len %v", len(plaintext))
148}
149// 8 for random number; 16 for md5 hash
150buffer := make([]byte, RandomNumberSize+CheckSumSize+len(plaintext))
151
152// add random
153random := rand2.Uint64()
154binary.LittleEndian.PutUint64(buffer[RandomNumberOffset:], random)
155
156// add request body
157copy(buffer[MessageOffset:], plaintext)
158
159// calculate and add checksum
160checksum := md5.Sum(buffer)
161copy(buffer[CheckSumOffset:], checksum[:])
162
163// encryption with aes CBC with keysize of 256-bit
164if cipher, err = AesEncryptCBC(key, buffer); err != nil {
165return
166}
167// base64 encoding
168message = base64.StdEncoding.EncodeToString(cipher)
169
170return
171}
172
173// DecodeMessage decode a message and verify its validity
174func DecodeMessage(message string, key []byte) (plaintext []byte, err error) {
175var (
176cipher []byte
177decodedText []byte
178)
179
180if cipher, err = base64.StdEncoding.DecodeString(message); err != nil {
181return
182}
183
184if decodedText, err = AesDecryptCBC(key, cipher); err != nil {
185return
186}
187
188if len(decodedText) <= MessageMetaDataSize {
189err = fmt.Errorf("invalid json format with size [%d] less than message meta data size", len(decodedText))
190return
191}
192
193msgChecksum := make([]byte, CheckSumSize)
194copy(msgChecksum, decodedText[CheckSumOffset:CheckSumOffset+CheckSumSize])
195
196// calculate checksum
197filltext := bytes.Repeat([]byte{byte(0)}, CheckSumSize)
198copy(decodedText[CheckSumOffset:], filltext[:])
199newChecksum := md5.Sum(decodedText)
200
201// verify checksum
202if !bytes.Equal(msgChecksum, newChecksum[:]) {
203err = fmt.Errorf("checksum not match")
204}
205
206plaintext = decodedText[MessageOffset:]
207
208// fmt.Printf("DecodeMessage CBC: %s\n", plaintext)
209return
210}
211
212// GenVerifier generate a verifier for replay mitigation in http
213func GenVerifier(key []byte) (v string, ts int64, err error) {
214ts = time.Now().Unix()
215tsbuf := make([]byte, unsafe.Sizeof(ts))
216binary.LittleEndian.PutUint64(tsbuf, uint64(ts))
217if v, err = EncodeMessage(tsbuf, key); err != nil {
218panic(err)
219}
220return
221}
222
223// CreateClientX creates a https client
224func CreateClientX(cert *[]byte) (client *http.Client, err error) {
225caCertPool := x509.NewCertPool()
226ok := caCertPool.AppendCertsFromPEM(*cert)
227
228if !ok {
229err = fmt.Errorf("CreateClientX AppendCertsFromPEM fails")
230return
231}
232
233// We don't use PKI to verify client since we have secret key for authentication
234client = &http.Client{
235Transport: &http.Transport{
236TLSClientConfig: &tls.Config{
237MinVersion: tls.VersionTLS12,
238RootCAs: caCertPool,
239InsecureSkipVerify: false,
240},
241},
242}
243return
244}
245