261 lines
7.6 KiB
Go
261 lines
7.6 KiB
Go
package oid
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/des"
|
|
"crypto/rand"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"golang.org/x/crypto/chacha20poly1305"
|
|
)
|
|
|
|
// EncryptionAlgorithm does the handling of the encrypton and decryption for a given algorithm identifier.
|
|
type EncryptionAlgorithm struct {
|
|
EncryptionAlgorithmIdentifier asn1.ObjectIdentifier
|
|
ContentEncryptionAlgorithmIdentifier pkix.AlgorithmIdentifier
|
|
Key, IV, MAC []byte
|
|
}
|
|
|
|
// Encryption Algorithm OIDs
|
|
var (
|
|
EncryptionAlgorithmDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7}
|
|
EncryptionAlgorithmDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7}
|
|
EncryptionAlgorithmAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2}
|
|
EncryptionAlgorithmAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
|
|
//AEAD
|
|
EncryptionAlgorithmAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6}
|
|
AEADChaCha20Poly1305 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 3, 18}
|
|
)
|
|
|
|
var symmetricKeyLen = map[string]int{
|
|
EncryptionAlgorithmDESCBC.String(): 7,
|
|
EncryptionAlgorithmDESEDE3CBC.String(): 21,
|
|
EncryptionAlgorithmAES128CBC.String(): 16,
|
|
EncryptionAlgorithmAES256CBC.String(): 32,
|
|
//AEAD
|
|
EncryptionAlgorithmAES128GCM.String(): 16,
|
|
AEADChaCha20Poly1305.String(): 32,
|
|
}
|
|
|
|
// Encrypt encrypts the plaintext and returns the ciphertext.
|
|
func (e *EncryptionAlgorithm) Encrypt(plaintext []byte) (ciphertext []byte, err error) {
|
|
|
|
if e.Key == nil {
|
|
e.Key = make([]byte, symmetricKeyLen[e.EncryptionAlgorithmIdentifier.String()])
|
|
rand.Read(e.Key)
|
|
}
|
|
|
|
//Choose cipher
|
|
var blockCipher cipher.Block
|
|
|
|
switch e.EncryptionAlgorithmIdentifier.String() {
|
|
case EncryptionAlgorithmAES128CBC.String(), EncryptionAlgorithmAES256CBC.String(), EncryptionAlgorithmAES128GCM.String():
|
|
blockCipher, err = aes.NewCipher(e.Key)
|
|
if err != nil {
|
|
return
|
|
}
|
|
case AEADChaCha20Poly1305.String():
|
|
default:
|
|
err = errors.New("Content encrytion: cipher not supportet")
|
|
return
|
|
}
|
|
|
|
//Choose blockmode
|
|
var blockMode cipher.BlockMode
|
|
var aead cipher.AEAD
|
|
switch e.EncryptionAlgorithmIdentifier.String() {
|
|
case EncryptionAlgorithmAES128CBC.String(), EncryptionAlgorithmAES256CBC.String():
|
|
if e.IV == nil {
|
|
e.IV = make([]byte, len(e.Key))
|
|
rand.Read(e.IV)
|
|
}
|
|
|
|
blockMode = cipher.NewCBCEncrypter(blockCipher, e.IV)
|
|
e.ContentEncryptionAlgorithmIdentifier = pkix.AlgorithmIdentifier{
|
|
Algorithm: e.EncryptionAlgorithmIdentifier,
|
|
Parameters: asn1.RawValue{Tag: 4, Bytes: e.IV}}
|
|
case EncryptionAlgorithmAES128GCM.String():
|
|
aead, err = cipher.NewGCM(blockCipher)
|
|
if err != nil {
|
|
return
|
|
}
|
|
case AEADChaCha20Poly1305.String():
|
|
aead, err = chacha20poly1305.New(e.Key)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
switch e.EncryptionAlgorithmIdentifier.String() {
|
|
case EncryptionAlgorithmAES128CBC.String(), EncryptionAlgorithmAES256CBC.String():
|
|
var plain []byte
|
|
plain, err = pad(plaintext, blockCipher.BlockSize())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ciphertext = make([]byte, len(plain))
|
|
|
|
blockMode.CryptBlocks(ciphertext, plain)
|
|
|
|
return
|
|
case EncryptionAlgorithmAES128GCM.String(), AEADChaCha20Poly1305.String():
|
|
nonce := make([]byte, nonceSize)
|
|
_, err = rand.Read(nonce)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ciphertext = aead.Seal(nil, nonce, plaintext, nil)
|
|
|
|
e.MAC = ciphertext[len(ciphertext)-aead.Overhead():]
|
|
ciphertext = ciphertext[:len(ciphertext)-aead.Overhead()]
|
|
switch e.EncryptionAlgorithmIdentifier.String() {
|
|
case EncryptionAlgorithmAES128GCM.String():
|
|
paramSeq := aesGCMParameters{
|
|
Nonce: nonce,
|
|
ICVLen: aead.Overhead(),
|
|
}
|
|
|
|
paramBytes, _ := asn1.Marshal(paramSeq)
|
|
|
|
e.ContentEncryptionAlgorithmIdentifier = pkix.AlgorithmIdentifier{
|
|
Algorithm: e.EncryptionAlgorithmIdentifier,
|
|
Parameters: asn1.RawValue{
|
|
Tag: asn1.TagSequence,
|
|
Bytes: paramBytes,
|
|
}}
|
|
case AEADChaCha20Poly1305.String():
|
|
e.ContentEncryptionAlgorithmIdentifier = pkix.AlgorithmIdentifier{
|
|
Algorithm: e.EncryptionAlgorithmIdentifier,
|
|
Parameters: asn1.RawValue{Tag: 4, Bytes: nonce}}
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
const nonceSize = 12
|
|
|
|
type aesGCMParameters struct {
|
|
Nonce []byte `asn1:"tag:4"`
|
|
ICVLen int
|
|
}
|
|
|
|
// Decrypt decrypts the ciphertext and returns the plaintext.
|
|
func (e *EncryptionAlgorithm) Decrypt(ciphertext []byte) (plaintext []byte, err error) {
|
|
|
|
e.EncryptionAlgorithmIdentifier = e.ContentEncryptionAlgorithmIdentifier.Algorithm
|
|
|
|
//Choose cipher
|
|
var blockCipher cipher.Block
|
|
|
|
switch e.EncryptionAlgorithmIdentifier.String() {
|
|
case EncryptionAlgorithmAES128CBC.String(), EncryptionAlgorithmAES256CBC.String(), EncryptionAlgorithmAES128GCM.String():
|
|
blockCipher, err = aes.NewCipher(e.Key)
|
|
if err != nil {
|
|
return
|
|
}
|
|
case EncryptionAlgorithmDESCBC.String():
|
|
blockCipher, err = des.NewCipher(e.Key)
|
|
fmt.Println("Warning: message is encoded with DES. DES should NOT be used.")
|
|
case EncryptionAlgorithmDESEDE3CBC.String():
|
|
blockCipher, err = des.NewTripleDESCipher(e.Key)
|
|
fmt.Println("Warning: message is encoded with 3DES. 3DES should NOT be used.")
|
|
case AEADChaCha20Poly1305.String():
|
|
default:
|
|
err = errors.New("Content encrytion: cipher not supportet")
|
|
return
|
|
}
|
|
|
|
//Choose blockmode
|
|
var blockMode cipher.BlockMode
|
|
var aead cipher.AEAD
|
|
switch e.EncryptionAlgorithmIdentifier.String() {
|
|
case EncryptionAlgorithmAES128CBC.String(), EncryptionAlgorithmAES256CBC.String(), EncryptionAlgorithmDESCBC.String(), EncryptionAlgorithmDESEDE3CBC.String():
|
|
e.IV = e.ContentEncryptionAlgorithmIdentifier.Parameters.Bytes
|
|
|
|
blockMode = cipher.NewCBCDecrypter(blockCipher, e.IV)
|
|
case EncryptionAlgorithmAES128GCM.String():
|
|
aead, err = cipher.NewGCM(blockCipher)
|
|
if err != nil {
|
|
return
|
|
}
|
|
case AEADChaCha20Poly1305.String():
|
|
aead, err = chacha20poly1305.New(e.Key)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
switch e.EncryptionAlgorithmIdentifier.String() {
|
|
case EncryptionAlgorithmAES128CBC.String(), EncryptionAlgorithmAES256CBC.String(), EncryptionAlgorithmDESCBC.String(), EncryptionAlgorithmDESEDE3CBC.String():
|
|
plaintext = make([]byte, len(ciphertext))
|
|
blockMode.CryptBlocks(plaintext, ciphertext)
|
|
|
|
return unpad(plaintext, blockMode.BlockSize())
|
|
case EncryptionAlgorithmAES128GCM.String(), AEADChaCha20Poly1305.String():
|
|
var cipher []byte
|
|
cipher = append(cipher, ciphertext...)
|
|
cipher = append(cipher, e.MAC...)
|
|
|
|
var nonce []byte
|
|
switch e.EncryptionAlgorithmIdentifier.String() {
|
|
case EncryptionAlgorithmAES128GCM.String():
|
|
params := aesGCMParameters{}
|
|
paramBytes := e.ContentEncryptionAlgorithmIdentifier.Parameters.Bytes
|
|
_, err = asn1.Unmarshal(paramBytes, ¶ms)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nonce = params.Nonce
|
|
case AEADChaCha20Poly1305.String():
|
|
nonce = e.ContentEncryptionAlgorithmIdentifier.Parameters.Bytes
|
|
}
|
|
|
|
plaintext, err = aead.Open(nil, nonce, cipher, nil)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func pad(data []byte, blocklen int) ([]byte, error) {
|
|
if blocklen < 1 {
|
|
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
|
}
|
|
padlen := blocklen - (len(data) % blocklen)
|
|
if padlen == 0 {
|
|
padlen = blocklen
|
|
}
|
|
pad := bytes.Repeat([]byte{byte(padlen)}, padlen)
|
|
return append(data, pad...), nil
|
|
}
|
|
|
|
func unpad(data []byte, blocklen int) ([]byte, error) {
|
|
if blocklen < 1 {
|
|
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
|
}
|
|
if len(data)%blocklen != 0 || len(data) == 0 {
|
|
return nil, fmt.Errorf("invalid data len %d", len(data))
|
|
}
|
|
|
|
// the last byte is the length of padding
|
|
padlen := int(data[len(data)-1])
|
|
|
|
// check padding integrity, all bytes should be the same
|
|
pad := data[len(data)-padlen:]
|
|
for _, padbyte := range pad {
|
|
if padbyte != byte(padlen) {
|
|
return nil, errors.New("invalid padding")
|
|
}
|
|
}
|
|
|
|
return data[:len(data)-padlen], nil
|
|
}
|