From 5f34d8256236a8cf0c9458e1c2f7552cef7bb87e Mon Sep 17 00:00:00 2001 From: InfiniteLoopSpace <35842605+InfiniteLoopSpace@users.noreply.github.com> Date: Mon, 10 Dec 2018 17:18:29 +0100 Subject: [PATCH] Added support for ECDH in enveloped data with ECDSA certificates. - fixed parsing of choice(was compatible with Apple mail.app which tagged kari explicitly) - minor fixes --- README.md | 2 +- asn1/asn1.go | 18 +-- asn1/marshal.go | 6 - cms/cms.go | 12 +- cms/cms_test.go | 253 ++++++++++++++++++++------------- cms/protocol/ecdh.go | 218 ++++++++++++++++++++++++++++ cms/protocol/envelopeddata.go | 2 +- cms/protocol/reciepientinfo.go | 49 ++++--- cms/protocol/signeddata.go | 8 +- cms/protocol/signerinfo.go | 10 +- oid/key_wrap.go | 163 +++++++++++++++++++++ oid/oid.go | 51 ++++++- oid/symmetric_ciphers.go | 16 ++- openssl/openssl.go | 60 +++++--- smime/smime_test.go | 2 +- 15 files changed, 702 insertions(+), 168 deletions(-) create mode 100644 cms/protocol/ecdh.go create mode 100644 oid/key_wrap.go diff --git a/README.md b/README.md index b55bc8a..3aedb12 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ It supports enveloped data with AES in CBC mode. Decryption also works with (3)D This is covered in - Cryptographic Message Syntax (CMS) Authenticated-Enveloped-Data Content Type [rfc5083](https://tools.ietf.org/html/rfc5083) - Using ChaCha20-Poly1305 Authenticated Encryption in the Cryptographic Message Syntax (CMS) [rfc8103](https://tools.ietf.org/html/rfc8103) -- Using AES-CCM and AES-GCM Authenticated Encryption in the Cryptographic Message Syntax (CMS) [rfc5084](https://tools.ietf.org/html/rfc 5084) +- Using AES-CCM and AES-GCM Authenticated Encryption in the Cryptographic Message Syntax (CMS) [rfc5084](https://tools.ietf.org/html/rfc5084) ## Examples diff --git a/asn1/asn1.go b/asn1/asn1.go index 7059e02..588d521 100644 --- a/asn1/asn1.go +++ b/asn1/asn1.go @@ -760,7 +760,7 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam if err != nil { return } - if params.explicit { + if params.explicit && !params.choice { expectedClass := ClassContextSpecific if params.application { expectedClass = ClassApplication @@ -968,19 +968,13 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam } innerOffset := 0 + if params.choice { + if !params.explicit { + innerBytes = bytes[initOffset:] + } + } for i := 0; i < structType.NumField(); i++ { field := structType.Field(i) - if params.choice { - tag := parseFieldParameters(field.Tag.Get("asn1")).tag - if tag != nil && t.tag == *tag || t.class != ClassContextSpecific && tag == nil { - if tag == nil || params.set { - innerBytes = bytes[initOffset:offset] - } - innerOffset, err = parseField(val.Field(i), innerBytes, innerOffset, fieldParameters{}) - return - } - continue - } if i == 0 && field.Type == rawContentsType { continue } diff --git a/asn1/marshal.go b/asn1/marshal.go index 0b30e63..7569ca9 100644 --- a/asn1/marshal.go +++ b/asn1/marshal.go @@ -482,9 +482,6 @@ func makeBody(value reflect.Value, params fieldParameters) (e encoder, err error m := make([]encoder, n1) for i := 0; i < n1; i++ { fp := parseFieldParameters(t.Field(i + startingField).Tag.Get("asn1")) - if params.explicit && params.choice { - fp.explicit = true - } m[i], err = makeField(v.Field(i+startingField), fp) if err != nil { return nil, err @@ -501,9 +498,6 @@ func makeBody(value reflect.Value, params fieldParameters) (e encoder, err error var fp fieldParameters fp.choice = params.choice - if params.choice && params.set { - fp.explicit = true - } switch l := v.Len(); l { case 0: diff --git a/cms/cms.go b/cms/cms.go index 5cdff9c..26fec20 100644 --- a/cms/cms.go +++ b/cms/cms.go @@ -65,7 +65,11 @@ func (cms *CMS) Encrypt(data []byte, recipients []*x509.Certificate) (der []byte var reciInfos []protocol.RecipientInfo for _, recipient := range recipients { - rInfo := protocol.NewRecipientInfo(recipient, key) + var rInfo protocol.RecipientInfo + rInfo, err = protocol.NewRecipientInfo(recipient, key) + if err != nil { + return + } reciInfos = append(reciInfos, rInfo) } @@ -90,7 +94,11 @@ func (cms *CMS) AuthEncrypt(data []byte, recipients []*x509.Certificate) (der [] var reciInfos []protocol.RecipientInfo for _, recipient := range recipients { - rInfo := protocol.NewRecipientInfo(recipient, key) + var rInfo protocol.RecipientInfo + rInfo, err = protocol.NewRecipientInfo(recipient, key) + if err != nil { + return + } reciInfos = append(reciInfos, rInfo) } diff --git a/cms/cms_test.go b/cms/cms_test.go index 8269843..e736fd0 100644 --- a/cms/cms_test.go +++ b/cms/cms_test.go @@ -3,10 +3,13 @@ package cms import ( "bytes" "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" - "log" + "strings" "testing" openssl "github.com/InfiniteLoopSpace/go_S-MIME/openssl" @@ -26,162 +29,216 @@ var ( CommonName: "leaf.example.com", })) + ecKey, _ = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + + leafECC = intermediate.Issue(pki.Subject(pkix.Name{ + CommonName: "leaf.example.com", + }), pki.PrivateKey(ecKey)) + keyPair = tls.Certificate{ Certificate: [][]byte{leaf.Certificate.Raw, intermediate.Certificate.Raw, root.Certificate.Raw}, PrivateKey: leaf.PrivateKey.(crypto.PrivateKey), + Leaf: leaf.Certificate, } + + keyPairECC = tls.Certificate{ + Certificate: [][]byte{leafECC.Certificate.Raw, intermediate.Certificate.Raw, root.Certificate.Raw}, + PrivateKey: leafECC.PrivateKey.(crypto.PrivateKey), + Leaf: leafECC.Certificate, + } + + keyPairs = []tls.Certificate{} + + keyPairsOpenssl = []tls.Certificate{} ) +func TestMain(m *testing.M) { + + keyPairs = []tls.Certificate{keyPair, keyPairECC} + + version, err := openssl.Openssl(nil, "version") + + if err == nil { + keyPairsOpenssl = append(keyPairsOpenssl, keyPair) + } + + if strings.HasPrefix(string(version), "OpenSSL 1.1") { + openssl.SMIME = "cms" + keyPairsOpenssl = append(keyPairsOpenssl, keyPairECC) + } + + m.Run() +} + func TestAuthEnrypt(t *testing.T) { - cms, err := New(keyPair) - if err != nil { - t.Error(err) - } + for _, keypair := range keyPairs { + cms, err := New(keypair) + if err != nil { + t.Error(err) + } - plaintext := []byte("Hallo Welt!") + plaintext := []byte("Hallo Welt!") - ciphertext, err := cms.AuthEncrypt(plaintext, []*x509.Certificate{leaf.Certificate}) - if err != nil { - t.Error(err) - } + ciphertext, err := cms.AuthEncrypt(plaintext, []*x509.Certificate{keypair.Leaf}) + if err != nil { + t.Error(err) + } - plain, err := cms.AuthDecrypt(ciphertext) - if err != nil { - log.Fatal(err) - } + plain, err := cms.AuthDecrypt(ciphertext) + if err != nil { + t.Error(err) + } - if !bytes.Equal(plaintext, plain) { - t.Fatal("Encryption and decryption are not inverse") + if !bytes.Equal(plaintext, plain) { + t.Fatal("Encryption and decryption are not inverse") + } } } func TestEnryptDecrypt(t *testing.T) { - cms, err := New(keyPair) - if err != nil { - t.Error(err) - } + for _, keypair := range keyPairs { + cms, err := New(keypair) + if err != nil { + t.Error(err) + } - plaintext := []byte("Hallo Welt!") + plaintext := []byte("Hallo Welt!") - ciphertext, err := cms.Encrypt(plaintext, []*x509.Certificate{leaf.Certificate}) - if err != nil { - t.Error(err) - } + ciphertext, err := cms.Encrypt(plaintext, []*x509.Certificate{keypair.Leaf}) + if err != nil { + t.Error(err) + } - plain, err := cms.Decrypt(ciphertext) - if err != nil { - log.Fatal(err) - } + plain, err := cms.Decrypt(ciphertext) + if err != nil { + t.Error(err) + } - if !bytes.Equal(plaintext, plain) { - t.Fatal("Encryption and decryption are not inverse") + if !bytes.Equal(plaintext, plain) { + t.Fatal("Encryption and decryption are not inverse") + } } } func TestSignVerify(t *testing.T) { - cms, err := New(keyPair) - if err != nil { - t.Error(err) - } - cms.roots.AddCert(root.Certificate) + for _, keypair := range keyPairs { + cms, err := New(keypair) + if err != nil { + t.Error(err) + } - msg := []byte("Hallo Welt!") + cms.roots.AddCert(root.Certificate) - der, err := cms.Sign(msg) - if err != nil { - t.Error(err) - } + msg := []byte("Hallo Welt!") - _, err = cms.Verify(der) - if err != nil { - t.Error(err) + der, err := cms.Sign(msg) + if err != nil { + t.Error(err) + } + + _, err = cms.Verify(der) + if err != nil { + t.Error(err) + } } } func TestEncryptOpenSSL(t *testing.T) { - message := []byte("Hallo Welt!") - der, err := openssl.Encrypt(message, leaf.Certificate, "-outform", "DER") - if err != nil { - t.Error(err) - } + for _, keypair := range keyPairsOpenssl { + message := []byte("Hallo Welt!") - cms, err := New(keyPair) - plain, err := cms.Decrypt(der) - if err != nil { - t.Error(err) - } + der, err := openssl.Encrypt(message, keypair.Leaf, "-outform", "DER") + if err != nil { + t.Error(err) + } - if !bytes.Equal(message, plain) { - t.Fatal("Encryption and decryption are not inverse") + cms, err := New(keypair) + plain, err := cms.Decrypt(der) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(message, plain) { + t.Fatal("Encryption and decryption are not inverse") + } } } func TestDecryptOpenSSL(t *testing.T) { - message := []byte("Hallo Welt!") - cms, _ := New() - ciphertext, err := cms.Encrypt(message, []*x509.Certificate{leaf.Certificate}) - if err != nil { - t.Error(err) - } + for _, keypair := range keyPairsOpenssl { + message := []byte("Hallo Welt!") - plain, err := openssl.Decrypt(ciphertext, leaf.PrivateKey, "-inform", "DER") - if err != nil { - t.Error(err) - } + cms, _ := New() + ciphertext, err := cms.Encrypt(message, []*x509.Certificate{keypair.Leaf}) + if err != nil { + t.Error(err) + } - if !bytes.Equal(message, plain) { - t.Fatal("Encryption and decryption are not inverse") + plain, err := openssl.Decrypt(ciphertext, keypair.PrivateKey, "-inform", "DER") + if err != nil { + t.Error(err) + } + + if !bytes.Equal(message, plain) { + t.Fatal("Encryption and decryption are not inverse") + } } } func TestSignOpenSSL(t *testing.T) { - message := []byte("Hallo Welt") - sig, err := openssl.SignDetached(message, leaf.Certificate, leaf.PrivateKey, []*x509.Certificate{intermediate.Certificate}, "-outform", "DER") - if err != nil { - t.Error(err) - } + for _, keypair := range keyPairsOpenssl { + message := []byte("Hallo Welt") - cms, err := New() - if err != nil { - t.Error(err) - } - cms.roots.AddCert(root.Certificate) + sig, err := openssl.SignDetached(message, keypair.Leaf, keypair.PrivateKey, []*x509.Certificate{intermediate.Certificate}, "-outform", "DER") + if err != nil { + t.Error(err) + } - _, err = cms.Verify(sig) - if err != nil { - t.Error(err) + cms, err := New() + if err != nil { + t.Error(err) + } + cms.roots.AddCert(root.Certificate) + + _, err = cms.Verify(sig) + if err != nil { + t.Error(err) + } } } func TestVerifyOpenSSL(t *testing.T) { - cms, err := New(keyPair) - if err != nil { - t.Error(err) - } - cms.TimeStamp = true + for _, keypair := range keyPairsOpenssl { + cms, err := New(keypair) + if err != nil { + t.Error(err) + } - cms.roots.AddCert(root.Certificate) + cms.TimeStamp = true - msg := []byte("Hallo Welt!") + cms.roots.AddCert(root.Certificate) - der, err := cms.Sign(msg) - if err != nil { - t.Error(err) - } + msg := []byte("Hallo Welt!") - sig, err := openssl.Verify(der, root.Certificate, "-inform", "DER") - if err != nil { - t.Error(err) - } + der, err := cms.Sign(msg) + if err != nil { + t.Error(err) + } - if !bytes.Equal(msg, sig) { - t.Fatal("Signed message and message do not agree!") + sig, err := openssl.Verify(der, root.Certificate, "-inform", "DER") + if err != nil { + t.Error(err) + } + + if !bytes.Equal(msg, sig) { + t.Fatal("Signed message and message do not agree!") + } } } diff --git a/cms/protocol/ecdh.go b/cms/protocol/ecdh.go new file mode 100644 index 0000000..3d054df --- /dev/null +++ b/cms/protocol/ecdh.go @@ -0,0 +1,218 @@ +package protocol + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/binary" + "errors" + "math/big" + + "github.com/InfiniteLoopSpace/go_S-MIME/oid" +) + +var errUnsupported = errors.New("Unsupported hash function") + +// ECDHsharedSecret computes shared secret with ephemeral static ECDH +func ECDHsharedSecret(curve elliptic.Curve, priv []byte, pubX, pubY *big.Int) []byte { + + x, _ := curve.ScalarMult(pubX, pubY, priv) + + return x.Bytes() +} + +// ANSIx963KDF implents ANSI X9.63 key derivation function +func ANSIx963KDF(sharedSecret, sharedInfo []byte, keyLen int, hash crypto.Hash) (key []byte, err error) { + + ctr := make([]byte, 4) + ctr[3] = 0x01 + if hash == 0 || !hash.Available() { + return nil, errUnsupported + } + h := hash.New() + + for i := 0; i < keyLen/hash.Size()+1; i++ { + h.Reset() + h.Write(sharedSecret) + h.Write(ctr) + h.Write(sharedInfo) + key = append(key, h.Sum(nil)...) + + // Increment counter + for i := len(ctr) - 1; i >= 0; i-- { + ctr[i]++ + if ctr[i] != 0 { + break + } + } + } + + return key[:keyLen], nil +} + +func encryptKeyECDH(key []byte, recipient *x509.Certificate) (kari KeyAgreeRecipientInfo, err error) { + + keyWrapAlgorithm := oid.KeyWrap{KeyWrapAlgorithm: oid.AES128Wrap} + keyEncryptionAlgorithm := oid.DHSinglePassstdDHsha256kdfscheme + hash := oid.KDFHashAlgorithm[keyEncryptionAlgorithm.String()] + + kari.UKM = make([]byte, 8) + rand.Read(kari.UKM) + + kari.Version = 3 + kari.Originator.OriginatorKey.Algorithm = pkix.AlgorithmIdentifier{Algorithm: oid.ECPublicKey} + + // check recipient key + + if recipient.PublicKeyAlgorithm != x509.ECDSA { + err = errors.New("Recipient certficiate has wrong public key algorithm, expected ECDSA") + return + } + + pubKey, ok := recipient.PublicKey.(*ecdsa.PublicKey) + if !ok { + err = errors.New("Can not parse public key of recipient") + return + } + + // genrate ephemeral public key and key encryption key + + priv, x, y, err := elliptic.GenerateKey(pubKey.Curve, rand.Reader) + if err != nil { + return + } + + ephPubKey := elliptic.Marshal(pubKey.Curve, x, y) + kari.Originator.OriginatorKey.PublicKey = asn1.BitString{Bytes: ephPubKey, BitLength: len(ephPubKey) * 8} + + sharedSecret := ECDHsharedSecret(pubKey.Curve, priv, pubKey.X, pubKey.Y) + + keyLenBigEnd := make([]byte, 4) + binary.BigEndian.PutUint32(keyLenBigEnd, uint32(keyWrapAlgorithm.KeyLen())*8) + sharedInfo := ECCCMSSharedInfo{KeyInfo: keyWrapAlgorithm.AlgorithmIdentifier(), + SuppPubInfo: keyLenBigEnd, + EntityUInfo: kari.UKM} + + sharedInfoDER, err := asn1.Marshal(sharedInfo) + + kek, err := ANSIx963KDF(sharedSecret, sharedInfoDER, keyWrapAlgorithm.KeyLen(), hash) + if err != nil { + return + } + + // encrypt key + + keyWrapAlgorithm.KEK = kek + encKey, err := keyWrapAlgorithm.Wrap(key) + if err != nil { + return + } + + keyWrapAlgorithmIdentifier, err := RawValue(keyWrapAlgorithm.AlgorithmIdentifier()) + if err != nil { + return + } + + kari.KeyEncryptionAlgorithm = pkix.AlgorithmIdentifier{Algorithm: keyEncryptionAlgorithm, + Parameters: keyWrapAlgorithmIdentifier} + + ias, err := NewIssuerAndSerialNumber(recipient) + karID := KeyAgreeRecipientIdentifier{IAS: ias} + + kari.RecipientEncryptedKeys = append(kari.RecipientEncryptedKeys, RecipientEncryptedKey{RID: karID, EncryptedKey: encKey}) + + return +} + +// ECCCMSSharedInfo ECC-CMS-SharedInfo ::= SEQUENCE { +// keyInfo AlgorithmIdentifier, +// entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, +// suppPubInfo [2] EXPLICIT OCTET STRING } +type ECCCMSSharedInfo struct { + KeyInfo pkix.AlgorithmIdentifier + EntityUInfo []byte `asn1:"optional,explicit,tag:0"` + SuppPubInfo []byte `asn1:"explicit,tag:2"` +} + +func (kari *KeyAgreeRecipientInfo) decryptKey(keyPair tls.Certificate) (key []byte, err error) { + + // check for ec key + + if kari.Version != 3 { + err = errors.New("Version not supported") + return + } + + if !kari.Originator.OriginatorKey.Algorithm.Algorithm.Equal(oid.ECPublicKey) { + err = errors.New("Orginator key algorithm not supported") + return + } + + pubKey, ok := keyPair.Leaf.PublicKey.(*ecdsa.PublicKey) + if !ok { + err = errors.New("Can not parse public key of recipient") + return + } + + x, y := elliptic.Unmarshal(pubKey.Curve, kari.Originator.OriginatorKey.PublicKey.Bytes) + + // genrate ephemeral public key and key encryption key + + priv := keyPair.PrivateKey.(*ecdsa.PrivateKey) + + privateKeyBytes := keyPair.PrivateKey.(*ecdsa.PrivateKey).D.Bytes() + paddedPrivateKey := make([]byte, (priv.Curve.Params().N.BitLen()+7)/8) + copy(paddedPrivateKey[len(paddedPrivateKey)-len(privateKeyBytes):], privateKeyBytes) + + sharedSecret := ECDHsharedSecret(pubKey.Curve, paddedPrivateKey, x, y) + + hash, ok := oid.KDFHashAlgorithm[kari.KeyEncryptionAlgorithm.Algorithm.String()] + if !ok { + err = errors.New("Unsupported key derivation hash algorithm") + return + } + + var keyWrapAlgorithmIdentifier pkix.AlgorithmIdentifier + asn1.Unmarshal(kari.KeyEncryptionAlgorithm.Parameters.FullBytes, &keyWrapAlgorithmIdentifier) + keyWrapAlgorithm := oid.KeyWrap{KeyWrapAlgorithm: keyWrapAlgorithmIdentifier.Algorithm} + + // + + keyLenBigEnd := make([]byte, 4) + binary.BigEndian.PutUint32(keyLenBigEnd, uint32(keyWrapAlgorithm.KeyLen())*8) + sharedInfo := ECCCMSSharedInfo{KeyInfo: keyWrapAlgorithmIdentifier, + SuppPubInfo: keyLenBigEnd, + EntityUInfo: kari.UKM} + + sharedInfoDER, err := asn1.Marshal(sharedInfo) + + kek, err := ANSIx963KDF(sharedSecret, sharedInfoDER, keyWrapAlgorithm.KeyLen(), hash) + if err != nil { + return + } + + keyWrapAlgorithm.KEK = kek + + // encrypt key + + ias, err := NewIssuerAndSerialNumber(keyPair.Leaf) + if err != nil { + return + } + + for i := range kari.RecipientEncryptedKeys { + if kari.RecipientEncryptedKeys[i].RID.IAS.Equal(ias) { + key, err = keyWrapAlgorithm.UnWrap(kari.RecipientEncryptedKeys[i].EncryptedKey) + return + } + } + + err = ErrNoKeyFound + + return +} diff --git a/cms/protocol/envelopeddata.go b/cms/protocol/envelopeddata.go index 06b635a..17ee42d 100644 --- a/cms/protocol/envelopeddata.go +++ b/cms/protocol/envelopeddata.go @@ -58,7 +58,7 @@ func (ed *EnvelopedData) decryptKey(keyPair tls.Certificate) (key []byte, err er for i := range ed.RecipientInfos { key, err = ed.RecipientInfos[i].decryptKey(keyPair) - if key != nil { + if key != nil || err != ErrNoKeyFound { return } } diff --git a/cms/protocol/reciepientinfo.go b/cms/protocol/reciepientinfo.go index be933bc..0956f46 100644 --- a/cms/protocol/reciepientinfo.go +++ b/cms/protocol/reciepientinfo.go @@ -33,8 +33,14 @@ type RecipientInfo struct { func (recInfo *RecipientInfo) decryptKey(keyPair tls.Certificate) (key []byte, err error) { - return recInfo.KTRI.decryptKey(keyPair) + key, err = recInfo.KTRI.decryptKey(keyPair) + if key != nil { + return + } + key, err = recInfo.KARI.decryptKey(keyPair) + + return } //KeyTransRecipientInfo ::= SEQUENCE { @@ -102,7 +108,7 @@ type RecipientIdentifier struct { } // NewRecipientInfo creates RecipientInfo for giben recipient and key. -func NewRecipientInfo(recipient *x509.Certificate, key []byte) RecipientInfo { +func NewRecipientInfo(recipient *x509.Certificate, key []byte) (info RecipientInfo, err error) { version := 0 //issuerAndSerialNumber rid := RecipientIdentifier{} @@ -118,24 +124,35 @@ func NewRecipientInfo(recipient *x509.Certificate, key []byte) RecipientInfo { rid.SKI = recipient.SubjectKeyId } - kea := oid.PublicKeyAlgorithmToEncrytionAlgorithm[recipient.PublicKeyAlgorithm] - if _, ok := oid.PublicKeyAlgorithmToEncrytionAlgorithm[recipient.PublicKeyAlgorithm]; !ok { - log.Fatal("NewRecipientInfo: PublicKeyAlgorithm not supported") + switch recipient.PublicKeyAlgorithm { + case x509.RSA: + var encrypted []byte + encrypted, err = encryptKeyRSA(key, recipient) + if err != nil { + return + } + info = RecipientInfo{ + KTRI: KeyTransRecipientInfo{ + Version: version, + Rid: rid, + KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oid.EncryptionAlgorithmRSA}, + EncryptedKey: encrypted, + }} + case x509.ECDSA: + var kari KeyAgreeRecipientInfo + kari, err = encryptKeyECDH(key, recipient) + if err != nil { + return + } + info = RecipientInfo{KARI: kari} + default: + err = errors.New("Public key algorithm not supported") } - encrypted, _ := encryptKey(key, recipient) - - info := RecipientInfo{ - KTRI: KeyTransRecipientInfo{ - Version: version, - Rid: rid, - KeyEncryptionAlgorithm: kea, - EncryptedKey: encrypted, - }} - return info + return } -func encryptKey(key []byte, recipient *x509.Certificate) ([]byte, error) { +func encryptKeyRSA(key []byte, recipient *x509.Certificate) ([]byte, error) { if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil { return rsa.EncryptPKCS1v15(rand.Reader, pub, key) } diff --git a/cms/protocol/signeddata.go b/cms/protocol/signeddata.go index a8d618b..8db4583 100644 --- a/cms/protocol/signeddata.go +++ b/cms/protocol/signeddata.go @@ -379,8 +379,12 @@ func (sd *SignedData) Verify(Opts x509.VerifyOptions, detached []byte) (chains [ return } } - - err = cert.CheckSignature(signer.X509SignatureAlgorithm(), signedMessage, signer.Signature) + var sigAlg x509.SignatureAlgorithm + sigAlg, err = signer.X509SignatureAlgorithm() + if err != nil { + return + } + err = cert.CheckSignature(sigAlg, signedMessage, signer.Signature) if err != nil { return } diff --git a/cms/protocol/signerinfo.go b/cms/protocol/signerinfo.go index f61e9d1..146c7d4 100644 --- a/cms/protocol/signerinfo.go +++ b/cms/protocol/signerinfo.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "fmt" "time" asn "github.com/InfiniteLoopSpace/go_S-MIME/asn1" @@ -83,13 +84,18 @@ func (si SignerInfo) Hash() (crypto.Hash, error) { // X509SignatureAlgorithm gets the x509.SignatureAlgorithm that should be used // for verifying this SignerInfo's signature. -func (si SignerInfo) X509SignatureAlgorithm() x509.SignatureAlgorithm { +func (si SignerInfo) X509SignatureAlgorithm() (sigAlg x509.SignatureAlgorithm, err error) { var ( sigOID = si.SignatureAlgorithm.Algorithm.String() digestOID = si.DigestAlgorithm.Algorithm.String() ) + sigAlg, ok := oid.SignatureAlgorithms[sigOID][digestOID] - return oid.SignatureAlgorithms[sigOID][digestOID] + if !ok { + err = fmt.Errorf("Signature algorithm with OID %s in combination with digest with OID %s not supported", sigOID, digestOID) + } + + return } // GetContentTypeAttribute gets the signed ContentType attribute from the diff --git a/oid/key_wrap.go b/oid/key_wrap.go new file mode 100644 index 0000000..4605484 --- /dev/null +++ b/oid/key_wrap.go @@ -0,0 +1,163 @@ +package oid + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/subtle" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/binary" + "errors" +) + +// KeyWrap wraps and unwraps key with the key encrytion key (KEK) for a given (KeyWrapAlgorithm) +type KeyWrap struct { + KEK []byte + KeyWrapAlgorithm asn1.ObjectIdentifier +} + +// Wrap wraps the content encryption key (cek) +func (kw *KeyWrap) Wrap(cek []byte) (ciphertext []byte, err error) { + + var block cipher.Block + switch kw.KeyWrapAlgorithm.String() { + case AES128Wrap.String(), AES192Wrap.String(), AES256Wrap.String(): + block, err = aes.NewCipher(kw.KEK) + if err != nil { + return + } + } + + return Wrap(block, cek) + +} + +// UnWrap unwraps the encrypted key (encKey) +func (kw *KeyWrap) UnWrap(encKey []byte) (cek []byte, err error) { + + var block cipher.Block + switch kw.KeyWrapAlgorithm.String() { + case AES128Wrap.String(), AES192Wrap.String(), AES256Wrap.String(): + block, err = aes.NewCipher(kw.KEK) + if err != nil { + return + } + } + + return Unwrap(block, encKey) + +} + +// KeyLen returns the key lenght of the key wrap algorithm +func (kw *KeyWrap) KeyLen() (len int) { + + switch kw.KeyWrapAlgorithm.String() { + case AES128Wrap.String(): + len = 16 + case AES192Wrap.String(): + len = 24 + case AES256Wrap.String(): + len = 32 + } + + return +} + +// AlgorithmIdentifier returns the OID of the key wrap algorithm +func (kw *KeyWrap) AlgorithmIdentifier() (algID pkix.AlgorithmIdentifier) { + + switch kw.KeyWrapAlgorithm.String() { + case AES128Wrap.String(), AES192Wrap.String(), AES256Wrap.String(): + algID = pkix.AlgorithmIdentifier{Algorithm: kw.KeyWrapAlgorithm} + } + + return +} + +// defaultIV from RFC-3394 +var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6} + +// Wrap encrypts the content encryption key (cek) with the given AES cipher (block), using the AES Key Wrap algorithm (RFC-3394) +func Wrap(block cipher.Block, cek []byte) (encKey []byte, err error) { + if len(cek)%8 != 0 { + return nil, errors.New("Lenght of cek must be in 8-byte blocks") + } + + // 1. Initialize variables + + // Set A = IV, an initial value (see 2.2.3) + B := make([]byte, 16) + copy(B, defaultIV) + + // For i = 1 to n + // R[i] = P[i] + encKey = make([]byte, len(cek)+8) + copy(encKey[8:], cek) + + n := len(cek) / 8 + + // 2. Calculate intermediate values. + for j := 0; j <= 5; j++ { + for i := 1; i <= n; i++ { + + // B = AES(K, A | R[i]) + copy(B[8:], encKey[i*8:(i+1)*8]) + block.Encrypt(B, B) + + // A = MSB(64, B) ^ t where t = (n*j)+i + t := uint64(n*j + i) + b := binary.BigEndian.Uint64(B[:8]) ^ t + binary.BigEndian.PutUint64(B[:8], b) + + // R[i] = LSB(64, B) + copy(encKey[i*8:(i+1)*8], B[8:]) + } + } + + // 3. Output the results. + copy(encKey[:8], B[:8]) + return +} + +// Unwrap decrypts the provided encrypted key (encKey) with the given AES cipher (block), using the AES Key Wrap algorithm (RFC-3394). +// Returns an error if validation fails. +func Unwrap(block cipher.Block, encKey []byte) (cek []byte, err error) { + if len(cek)%8 != 0 { + return nil, errors.New("Length of encKey must multiple 8-bytes") + } + + //Initialize variables + B := make([]byte, 16) + copy(B, encKey[:8]) + + cek = make([]byte, len(encKey)-8) + copy(cek, encKey[8:]) + + n := (len(encKey) / 8) - 1 + + //Compute intermediate values + for j := 5; j >= 0; j-- { + for i := n; i >= 1; i-- { + + // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i + copy(B[8:], cek[(i-1)*8:i*8]) + t := uint64(n*j + i) + b := binary.BigEndian.Uint64(B[:8]) ^ t + binary.BigEndian.PutUint64(B[:8], b) + + block.Decrypt(B, B) + + // A = MSB(64, B) + // R[i] = LSB(64, B) + copy(cek[(i-1)*8:i*8], B[8:]) + + } + } + + if subtle.ConstantTimeCompare(B[:8], defaultIV) != 1 { + return nil, errors.New("Integrity check failed - unexpected IV") + } + + //Output + return +} diff --git a/oid/oid.go b/oid/oid.go index ab36663..e58ec68 100644 --- a/oid/oid.go +++ b/oid/oid.go @@ -28,8 +28,13 @@ var ( // Signature Algorithm OIDs var ( - SignatureAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} - SignatureAlgorithmECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} + SignatureAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + SignatureAlgorithmECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} + SignatureAlgorithmECDSAwithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} + SignatureAlgorithmECDSAwithSHA224 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 1} + SignatureAlgorithmECDSAwithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + SignatureAlgorithmECDSAwithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + SignatureAlgorithmECDSAwithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} ) // Public Key Encryption OIDs @@ -51,6 +56,27 @@ var ( SubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} ) +// Elliptic curve public key OID +var ( + ECPublicKey = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} +) + +// DH Key Derivation Schemes OIDs +var ( + DHSinglePassstdDHsha1kdfscheme = asn1.ObjectIdentifier{1, 3, 133, 16, 840, 63, 0, 2} + DHSinglePassstdDHsha224kdfscheme = asn1.ObjectIdentifier{1, 3, 132, 1, 11, 0} + DHSinglePassstdDHsha256kdfscheme = asn1.ObjectIdentifier{1, 3, 132, 1, 11, 1} + DHSinglePassstdDHsha384kdfscheme = asn1.ObjectIdentifier{1, 3, 132, 1, 11, 2} + DHSinglePassstdDHsha512kdfscheme = asn1.ObjectIdentifier{1, 3, 132, 1, 11, 3} +) + +// Key wrap algorithm OIDs +var ( + AES128Wrap = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 5} + AES192Wrap = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 25} + AES256Wrap = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 45} +) + // DigestAlgorithmToHash maps digest OIDs to crypto.Hash values. var DigestAlgorithmToHash = map[string]crypto.Hash{ DigestAlgorithmSHA1.String(): crypto.SHA1, @@ -113,6 +139,18 @@ var SignatureAlgorithms = map[string]map[string]x509.SignatureAlgorithm{ DigestAlgorithmSHA384.String(): x509.ECDSAWithSHA384, DigestAlgorithmSHA512.String(): x509.ECDSAWithSHA512, }, + SignatureAlgorithmECDSAwithSHA1.String(): map[string]x509.SignatureAlgorithm{ + DigestAlgorithmSHA1.String(): x509.ECDSAWithSHA1, + }, + SignatureAlgorithmECDSAwithSHA256.String(): map[string]x509.SignatureAlgorithm{ + DigestAlgorithmSHA256.String(): x509.ECDSAWithSHA256, + }, + SignatureAlgorithmECDSAwithSHA384.String(): map[string]x509.SignatureAlgorithm{ + DigestAlgorithmSHA384.String(): x509.ECDSAWithSHA384, + }, + SignatureAlgorithmECDSAwithSHA512.String(): map[string]x509.SignatureAlgorithm{ + DigestAlgorithmSHA512.String(): x509.ECDSAWithSHA512, + }, } // PublicKeyAlgorithmToSignatureAlgorithm maps certificate public key @@ -127,3 +165,12 @@ var PublicKeyAlgorithmToSignatureAlgorithm = map[x509.PublicKeyAlgorithm]pkix.Al var PublicKeyAlgorithmToEncrytionAlgorithm = map[x509.PublicKeyAlgorithm]pkix.AlgorithmIdentifier{ x509.RSA: pkix.AlgorithmIdentifier{Algorithm: EncryptionAlgorithmRSA}, } + +// KDFHashAlgorithm key derivation schemes to its hash algorithms +var KDFHashAlgorithm = map[string]crypto.Hash{ + DHSinglePassstdDHsha1kdfscheme.String(): crypto.SHA1, + DHSinglePassstdDHsha224kdfscheme.String(): crypto.SHA224, + DHSinglePassstdDHsha256kdfscheme.String(): crypto.SHA256, + DHSinglePassstdDHsha384kdfscheme.String(): crypto.SHA384, + DHSinglePassstdDHsha512kdfscheme.String(): crypto.SHA512, +} diff --git a/oid/symmetric_ciphers.go b/oid/symmetric_ciphers.go index 8bb1098..5bad88e 100644 --- a/oid/symmetric_ciphers.go +++ b/oid/symmetric_ciphers.go @@ -32,9 +32,10 @@ var ( AEADChaCha20Poly1305 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 3, 18} ) -var symmetricKeyLen = map[string]int{ - EncryptionAlgorithmDESCBC.String(): 7, - EncryptionAlgorithmDESEDE3CBC.String(): 21, +// SymmetricKeyLen maps the encryption algorithm to its key length +var SymmetricKeyLen = map[string]int{ + EncryptionAlgorithmDESCBC.String(): 8, + EncryptionAlgorithmDESEDE3CBC.String(): 24, EncryptionAlgorithmAES128CBC.String(): 16, EncryptionAlgorithmAES256CBC.String(): 32, //AEAD @@ -46,7 +47,7 @@ var symmetricKeyLen = map[string]int{ func (e *EncryptionAlgorithm) Encrypt(plaintext []byte) (ciphertext []byte, err error) { if e.Key == nil { - e.Key = make([]byte, symmetricKeyLen[e.EncryptionAlgorithmIdentifier.String()]) + e.Key = make([]byte, SymmetricKeyLen[e.EncryptionAlgorithmIdentifier.String()]) rand.Read(e.Key) } @@ -71,7 +72,7 @@ func (e *EncryptionAlgorithm) Encrypt(plaintext []byte) (ciphertext []byte, err switch e.EncryptionAlgorithmIdentifier.String() { case EncryptionAlgorithmAES128CBC.String(), EncryptionAlgorithmAES256CBC.String(): if e.IV == nil { - e.IV = make([]byte, len(e.Key)) + e.IV = make([]byte, blockCipher.BlockSize()) rand.Read(e.IV) } @@ -180,7 +181,6 @@ func (e *EncryptionAlgorithm) Decrypt(ciphertext []byte) (plaintext []byte, err 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) @@ -198,7 +198,6 @@ func (e *EncryptionAlgorithm) Decrypt(ciphertext []byte) (plaintext []byte, err 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 @@ -247,6 +246,9 @@ func unpad(data []byte, blocklen int) ([]byte, error) { // the last byte is the length of padding padlen := int(data[len(data)-1]) + if padlen > blocklen { + return nil, fmt.Errorf("pad len %d is bigger than block len len %d", padlen, blocklen) + } // check padding integrity, all bytes should be the same pad := data[len(data)-padlen:] diff --git a/openssl/openssl.go b/openssl/openssl.go index e58fb6d..1f2a239 100644 --- a/openssl/openssl.go +++ b/openssl/openssl.go @@ -4,26 +4,29 @@ package openssl import ( "bytes" "crypto" - "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "io/ioutil" "os" "os/exec" + "strings" ) +// SMIME is the commpand used for openssl smime, can be replaces with cms +var SMIME = "smime" + //Encrypt a message with openssl func Encrypt(in []byte, cert *x509.Certificate, opts ...string) (der []byte, err error) { - tmp, err := ioutil.TempFile("", "example") - defer os.Remove(tmp.Name()) + tmpKey, err := ioutil.TempFile("", "example") + defer os.Remove(tmpKey.Name()) - pem.Encode(tmp, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + pem.Encode(tmpKey, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) - param := []string{"smime", "-encrypt", "-aes128"} + param := []string{SMIME, "-encrypt", "-aes128"} param = append(param, opts...) - param = append(param, tmp.Name()) + param = append(param, tmpKey.Name()) der, err = openssl(in, param...) return @@ -32,20 +35,24 @@ func Encrypt(in []byte, cert *x509.Certificate, opts ...string) (der []byte, err //Decrypt a message with openssl func Decrypt(in []byte, key crypto.PrivateKey, opts ...string) (plain []byte, err error) { - tmp, err := ioutil.TempFile("", "example") - defer os.Remove(tmp.Name()) + tmpKey, err := ioutil.TempFile("", "example") + defer os.Remove(tmpKey.Name()) - pem.Encode(tmp, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey))}) + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return + } + pem.Encode(tmpKey, &pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) - param := []string{"smime", "-decrypt"} + param := []string{SMIME, "-decrypt"} param = append(param, opts...) - param = append(param, []string{"-decrypt", "-inkey", tmp.Name()}...) + param = append(param, []string{"-inkey", tmpKey.Name()}...) plain, err = openssl(in, param...) return } -//Create a detached signature with openssl +// SignDetached creates a detached signature with openssl func SignDetached(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm []*x509.Certificate, opts ...string) (plain []byte, err error) { tmpCert, err := ioutil.TempFile("", "example") @@ -56,7 +63,11 @@ func SignDetached(in []byte, cert *x509.Certificate, key crypto.PrivateKey, inte tmpKey, err := ioutil.TempFile("", "example") defer os.Remove(tmpKey.Name()) - pem.Encode(tmpKey, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey))}) + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return + } + pem.Encode(tmpKey, &pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) tmpInterm, err := ioutil.TempFile("", "example") defer os.Remove(tmpInterm.Name()) @@ -65,7 +76,7 @@ func SignDetached(in []byte, cert *x509.Certificate, key crypto.PrivateKey, inte pem.Encode(tmpInterm, &pem.Block{Type: "CERTIFICATE", Bytes: i.Raw}) } - param := []string{"smime", "-sign", "-nodetach"} + param := []string{SMIME, "-sign", "-nodetach"} param = append(param, opts...) param = append(param, []string{"-signer", tmpCert.Name(), "-inkey", tmpKey.Name(), "-certfile", tmpInterm.Name()}...) plain, err = openssl(in, param...) @@ -73,7 +84,7 @@ func SignDetached(in []byte, cert *x509.Certificate, key crypto.PrivateKey, inte return } -//Create a signature with openssl +// Sign creates a signature with openssl func Sign(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm []*x509.Certificate, opts ...string) (plain []byte, err error) { tmpCert, err := ioutil.TempFile("", "example") @@ -84,7 +95,11 @@ func Sign(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm []*x5 tmpKey, err := ioutil.TempFile("", "example") defer os.Remove(tmpKey.Name()) - pem.Encode(tmpKey, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey))}) + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return + } + pem.Encode(tmpKey, &pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) tmpInterm, err := ioutil.TempFile("", "example") defer os.Remove(tmpInterm.Name()) @@ -93,7 +108,7 @@ func Sign(in []byte, cert *x509.Certificate, key crypto.PrivateKey, interm []*x5 pem.Encode(tmpInterm, &pem.Block{Type: "CERTIFICATE", Bytes: i.Raw}) } - param := []string{"smime", "-sign"} + param := []string{SMIME, "-sign"} param = append(param, opts...) param = append(param, []string{"-signer", tmpCert.Name(), "-inkey", tmpKey.Name(), "-certfile", tmpInterm.Name()}...) plain, err = openssl(in, param...) @@ -109,7 +124,7 @@ func Verify(in []byte, ca *x509.Certificate, opts ...string) (plain []byte, err pem.Encode(tmpCA, &pem.Block{Type: "CERTIFICATE", Bytes: ca.Raw}) - param := []string{"smime", "-verify"} + param := []string{SMIME, "-verify"} param = append(param, opts...) param = append(param, []string{"-CAfile", tmpCA.Name()}...) plain, err = openssl(in, param...) @@ -117,6 +132,11 @@ func Verify(in []byte, ca *x509.Certificate, opts ...string) (plain []byte, err return } +// Openssl runs the openssl command with given args +func Openssl(stdin []byte, args ...string) ([]byte, error) { + return openssl(stdin, args...) +} + func openssl(stdin []byte, args ...string) ([]byte, error) { cmd := exec.Command("openssl", args...) @@ -133,5 +153,9 @@ func openssl(stdin []byte, args ...string) ([]byte, error) { return nil, err } + if strings.Contains(errs.String(), "Error") { + return nil, fmt.Errorf("error running %s (%s):\n ", cmd.Args, errs.String()) + } + return out.Bytes(), nil } diff --git a/smime/smime_test.go b/smime/smime_test.go index cc3a4b2..f2e4cab 100644 --- a/smime/smime_test.go +++ b/smime/smime_test.go @@ -234,7 +234,7 @@ Y0ZB9qANMAsGA1UdDzEEAwIAEA== -----END PRIVATE KEY-----` //https://github.com/fullsailor/pkcs7/issues/9 -func TestiTunesReceipt(t *testing.T) { +func TestSampleiTunesReceipt(t *testing.T) { b, err := base64.StdEncoding.DecodeString(strings.TrimSpace(iTunesReceipt)) if err != nil {