From 9179582f90c52a52c22da097276c689907f8a96e Mon Sep 17 00:00:00 2001 From: InfiniteLoopSpace <35842605+InfiniteLoopSpace@users.noreply.github.com> Date: Thu, 15 Nov 2018 12:29:43 +0100 Subject: [PATCH] Added oid which is a fork of https://github.com/mastahyeti/cms/tree/master/oid; contains the needed OIDs for cryptographic message syntax. --- oid/oid.go | 129 +++++++++++++++++++ oid/symmetric_ciphers.go | 260 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 389 insertions(+) create mode 100644 oid/oid.go create mode 100644 oid/symmetric_ciphers.go diff --git a/oid/oid.go b/oid/oid.go new file mode 100644 index 0000000..ab36663 --- /dev/null +++ b/oid/oid.go @@ -0,0 +1,129 @@ +// Package oid contains OIDs that are used by other packages in this repository. +package oid + +import ( + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" +) + +// Content type OIDs +var ( + Data = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} + SignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} + EnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 3} + AuthEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 23} + TSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4} + ContentTypeTSTInfo = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 1, 4} +) + +// Attribute OIDs +var ( + AttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} + AttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} + AttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} + AttributeTimeStampToken = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14} +) + +// Signature Algorithm OIDs +var ( + SignatureAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + SignatureAlgorithmECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} +) + +// Public Key Encryption OIDs +var ( + EncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} +) + +// Digest Algorithm OIDs +var ( + DigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} + DigestAlgorithmMD5 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 5} + DigestAlgorithmSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} + DigestAlgorithmSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} + DigestAlgorithmSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} +) + +// X.509 extensions +var ( + SubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} +) + +// DigestAlgorithmToHash maps digest OIDs to crypto.Hash values. +var DigestAlgorithmToHash = map[string]crypto.Hash{ + DigestAlgorithmSHA1.String(): crypto.SHA1, + DigestAlgorithmMD5.String(): crypto.MD5, + DigestAlgorithmSHA256.String(): crypto.SHA256, + DigestAlgorithmSHA384.String(): crypto.SHA384, + DigestAlgorithmSHA512.String(): crypto.SHA512, +} + +// HashToDigestAlgorithm maps crypto.Hash values to digest OIDs. +var HashToDigestAlgorithm = map[crypto.Hash]asn1.ObjectIdentifier{ + crypto.SHA1: DigestAlgorithmSHA1, + crypto.MD5: DigestAlgorithmMD5, + crypto.SHA256: DigestAlgorithmSHA256, + crypto.SHA384: DigestAlgorithmSHA384, + crypto.SHA512: DigestAlgorithmSHA512, +} + +// SignatureAlgorithmToDigestAlgorithm maps x509.SignatureAlgorithm to +// digestAlgorithm OIDs. +var SignatureAlgorithmToDigestAlgorithm = map[x509.SignatureAlgorithm]asn1.ObjectIdentifier{ + x509.SHA1WithRSA: DigestAlgorithmSHA1, + x509.MD5WithRSA: DigestAlgorithmMD5, + x509.SHA256WithRSA: DigestAlgorithmSHA256, + x509.SHA384WithRSA: DigestAlgorithmSHA384, + x509.SHA512WithRSA: DigestAlgorithmSHA512, + x509.ECDSAWithSHA1: DigestAlgorithmSHA1, + x509.ECDSAWithSHA256: DigestAlgorithmSHA256, + x509.ECDSAWithSHA384: DigestAlgorithmSHA384, + x509.ECDSAWithSHA512: DigestAlgorithmSHA512, +} + +// SignatureAlgorithmToSignatureAlgorithm maps x509.SignatureAlgorithm to +// signatureAlgorithm OIDs. +var SignatureAlgorithmToSignatureAlgorithm = map[x509.SignatureAlgorithm]asn1.ObjectIdentifier{ + x509.SHA1WithRSA: SignatureAlgorithmRSA, + x509.MD5WithRSA: SignatureAlgorithmRSA, + x509.SHA256WithRSA: SignatureAlgorithmRSA, + x509.SHA384WithRSA: SignatureAlgorithmRSA, + x509.SHA512WithRSA: SignatureAlgorithmRSA, + x509.ECDSAWithSHA1: SignatureAlgorithmECDSA, + x509.ECDSAWithSHA256: SignatureAlgorithmECDSA, + x509.ECDSAWithSHA384: SignatureAlgorithmECDSA, + x509.ECDSAWithSHA512: SignatureAlgorithmECDSA, +} + +// SignatureAlgorithms maps digest and signature OIDs to +// x509.SignatureAlgorithm values. +var SignatureAlgorithms = map[string]map[string]x509.SignatureAlgorithm{ + SignatureAlgorithmRSA.String(): map[string]x509.SignatureAlgorithm{ + DigestAlgorithmSHA1.String(): x509.SHA1WithRSA, + DigestAlgorithmMD5.String(): x509.MD5WithRSA, + DigestAlgorithmSHA256.String(): x509.SHA256WithRSA, + DigestAlgorithmSHA384.String(): x509.SHA384WithRSA, + DigestAlgorithmSHA512.String(): x509.SHA512WithRSA, + }, + SignatureAlgorithmECDSA.String(): map[string]x509.SignatureAlgorithm{ + DigestAlgorithmSHA1.String(): x509.ECDSAWithSHA1, + DigestAlgorithmSHA256.String(): x509.ECDSAWithSHA256, + DigestAlgorithmSHA384.String(): x509.ECDSAWithSHA384, + DigestAlgorithmSHA512.String(): x509.ECDSAWithSHA512, + }, +} + +// PublicKeyAlgorithmToSignatureAlgorithm maps certificate public key +// algorithms to CMS signature algorithms. +var PublicKeyAlgorithmToSignatureAlgorithm = map[x509.PublicKeyAlgorithm]pkix.AlgorithmIdentifier{ + x509.RSA: pkix.AlgorithmIdentifier{Algorithm: SignatureAlgorithmRSA}, + x509.ECDSA: pkix.AlgorithmIdentifier{Algorithm: SignatureAlgorithmECDSA}, +} + +// PublicKeyAlgorithmToEncrytionAlgorithm maps certificate public key +// algorithms to CMS encryption algorithms. +var PublicKeyAlgorithmToEncrytionAlgorithm = map[x509.PublicKeyAlgorithm]pkix.AlgorithmIdentifier{ + x509.RSA: pkix.AlgorithmIdentifier{Algorithm: EncryptionAlgorithmRSA}, +} diff --git a/oid/symmetric_ciphers.go b/oid/symmetric_ciphers.go new file mode 100644 index 0000000..8bb1098 --- /dev/null +++ b/oid/symmetric_ciphers.go @@ -0,0 +1,260 @@ +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 +}