From 3f58f9a4b2b6e023647ce790b3462ba6110dbc3b Mon Sep 17 00:00:00 2001 From: InfiniteLoopSpace <35842605+InfiniteLoopSpace@users.noreply.github.com> Date: Fri, 21 Dec 2018 14:43:59 +0100 Subject: [PATCH] Added support for RSASSA-PSS and RSAES-OAEP. --- README.md | 6 +- cms/cms.go | 19 ++- cms/cms_test.go | 294 ++++++++++++++++++--------------- cms/protocol/pssoaep.go | 183 ++++++++++++++++++++ cms/protocol/reciepientinfo.go | 107 +++++++----- cms/protocol/signeddata.go | 22 ++- oid/oid.go | 9 +- openssl/openssl.go | 14 +- pki/pki.go | 9 + smime/smime.go | 28 ++++ 10 files changed, 510 insertions(+), 181 deletions(-) create mode 100644 cms/protocol/pssoaep.go diff --git a/README.md b/README.md index 3aedb12..de5fca3 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,14 @@ It consists of the following packages - smime Secure/Multipurpose Internet Mail Extensions (S/MIME) Version 4.0 [rfc5751-bis-12](https://tools.ietf.org/html/draft-ietf-lamps-rfc5751-bis-12) [![GoDoc](https://godoc.org/github.com/InfiniteLoopSpace/go_S-MIME/smime?status.svg)](https://godoc.org/github.com/InfiniteLoopSpace/go_S-MIME/smime) - timestamp[5] - Time-Stamp Protocol (TSP) [rfc3161](https://tools.ietf.org/html/rfc3161) [![GoDoc](https://godoc.org/github.com/InfiniteLoopSpace/go_S-MIME/timestamp?status.svg)](https://godoc.org/github.com/InfiniteLoopSpace/go_S-MIME/timestamp) -It supports enveloped data with AES in CBC mode. Decryption also works with (3)DES. Authenticated-Enveloped-Data Content Type is also supported with AES-GCM and ChaCha20-Poly1305. +It supports enveloped data with AES in CBC mode. Decryption also works with (3)DES. Authenticated-Enveloped-Data Content Type is also supported with AES-GCM and ChaCha20-Poly1305. Also RSAES-OAEP and RSASSA-PSS is supported. 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/rfc5084) +- Use of the RSASSA-PSS Signature Algorithm in Cryptographic Message Syntax (CMS) [rfc4056](https://tools.ietf.org/html/rfc4056) +- Use of the RSAES-OAEP Key Transport Algorithm in the Cryptographic Message Syntax (CMS) [rfc3560](https://tools.ietf.org/html/rfc3560) ## Examples @@ -53,8 +55,6 @@ plaintext, _ := SMIME.Verify(signedMsg) ## Todo -- Add S/MIME capabilities attributes -- Add ECDH for encryption and decryption - Testing diff --git a/cms/cms.go b/cms/cms.go index 26fec20..3744d67 100644 --- a/cms/cms.go +++ b/cms/cms.go @@ -23,6 +23,7 @@ type CMS struct { TimeStampServer string TimeStamp bool keyPairs []tls.Certificate + signedAttrs []protocol.Attribute } // New create a new instance of CMS with given keyPairs. @@ -54,6 +55,19 @@ func New(cert ...tls.Certificate) (cms *CMS, err error) { return } +// AddAttribute adds a attribute to signedAttrs which will be used for signing +func (cms *CMS) AddAttribute(attrType asn1.ObjectIdentifier, val interface{}) (err error) { + + attr, err := protocol.NewAttribute(attrType, val) + if err != nil { + return + } + + cms.signedAttrs = append(cms.signedAttrs, attr) + + return +} + // Encrypt encrypts data for the recipients and returns DER-encoded ASN.1 ContentInfo. func (cms *CMS) Encrypt(data []byte, recipients []*x509.Certificate) (der []byte, err error) { @@ -160,7 +174,10 @@ func (cms *CMS) Sign(data []byte, detachedSignature ...bool) (der []byte, err er } for i := range cms.keyPairs { - sd.AddSignerInfo(cms.keyPairs[i]) + err = sd.AddSignerInfo(cms.keyPairs[i], cms.signedAttrs) + if err != nil { + return + } } if cms.TimeStamp { diff --git a/cms/cms_test.go b/cms/cms_test.go index e736fd0..9b367b1 100644 --- a/cms/cms_test.go +++ b/cms/cms_test.go @@ -29,216 +29,250 @@ var ( CommonName: "leaf.example.com", })) + leafPSS = intermediate.Issue(pki.Subject(pkix.Name{ + CommonName: "leaf.example.com", + }), pki.SignatureAlgorithm(x509.SHA256WithRSAPSS)) + ecKey, _ = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) leafECC = intermediate.Issue(pki.Subject(pkix.Name{ CommonName: "leaf.example.com", }), pki.PrivateKey(ecKey)) - keyPair = tls.Certificate{ + keyPairRSA = tls.Certificate{ Certificate: [][]byte{leaf.Certificate.Raw, intermediate.Certificate.Raw, root.Certificate.Raw}, PrivateKey: leaf.PrivateKey.(crypto.PrivateKey), Leaf: leaf.Certificate, } + keyPairRSAPSS = tls.Certificate{ + Certificate: [][]byte{leafPSS.Certificate.Raw, intermediate.Certificate.Raw, root.Certificate.Raw}, + PrivateKey: leafPSS.PrivateKey.(crypto.PrivateKey), + Leaf: leafPSS.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{} + keypair tls.Certificate - keyPairsOpenssl = []tls.Certificate{} + skipOpenssl = false + + opensslSignOpts = []string{"-outform", "DER"} + + opensslEncOpts = []string{"-outform", "DER"} ) func TestMain(m *testing.M) { - - keyPairs = []tls.Certificate{keyPair, keyPairECC} + // Test RSA + keypair = keyPairRSA version, err := openssl.Openssl(nil, "version") - - if err == nil { - keyPairsOpenssl = append(keyPairsOpenssl, keyPair) + if err != nil { + skipOpenssl = true } + m.Run() + + // Test RSA PSS OAEP + keypair = keyPairRSAPSS + if strings.HasPrefix(string(version), "OpenSSL 1.1") { openssl.SMIME = "cms" - keyPairsOpenssl = append(keyPairsOpenssl, keyPairECC) + } else { + skipOpenssl = true } + opensslSignOpts = append(opensslSignOpts, "-keyopt", "rsa_padding_mode:pss") + opensslEncOpts = append(opensslEncOpts, "-keyopt", "rsa_padding_mode:oaep") + + m.Run() + opensslSignOpts = []string{"-outform", "DER"} + opensslEncOpts = []string{"-outform", "DER"} + + // Test ECDDSA + keypair = keyPairECC + m.Run() } func TestAuthEnrypt(t *testing.T) { - for _, keypair := range keyPairs { - cms, err := New(keypair) - if err != nil { - t.Error(err) - } + 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{keypair.Leaf}) - 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 { - t.Error(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) { - for _, keypair := range keyPairs { - cms, err := New(keypair) - if err != nil { - t.Error(err) - } - - plaintext := []byte("Hallo Welt!") - - ciphertext, err := cms.Encrypt(plaintext, []*x509.Certificate{keypair.Leaf}) - if err != nil { - t.Error(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") - } + cms, err := New(keypair) + if err != nil { + t.Error(err) } + + plaintext := []byte("Hallo Welt!") + + ciphertext, err := cms.Encrypt(plaintext, []*x509.Certificate{keypair.Leaf}) + if err != nil { + t.Error(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") + } + } func TestSignVerify(t *testing.T) { - for _, keypair := range keyPairs { - cms, err := New(keypair) - if err != nil { - t.Error(err) - } + cms, err := New(keypair) + if err != nil { + t.Error(err) + } - cms.roots.AddCert(root.Certificate) + cms.roots.AddCert(root.Certificate) - msg := []byte("Hallo Welt!") + msg := []byte("Hallo Welt!") - der, err := cms.Sign(msg) - 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) - } + _, err = cms.Verify(der) + if err != nil { + t.Error(err) } } func TestEncryptOpenSSL(t *testing.T) { - for _, keypair := range keyPairsOpenssl { - message := []byte("Hallo Welt!") - - der, err := openssl.Encrypt(message, keypair.Leaf, "-outform", "DER") - if err != nil { - t.Error(err) - } - - 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") - } + if skipOpenssl { + return } + + message := []byte("Hallo Welt!") + + der, err := openssl.Encrypt(message, keypair.Leaf, opensslEncOpts...) + if err != nil { + t.Error(err) + } + + 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) { - for _, keypair := range keyPairsOpenssl { - message := []byte("Hallo Welt!") - - cms, _ := New() - ciphertext, err := cms.Encrypt(message, []*x509.Certificate{keypair.Leaf}) - if err != nil { - t.Error(err) - } - - 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") - } + if skipOpenssl { + return } + + message := []byte("Hallo Welt!") + + cms, _ := New() + ciphertext, err := cms.Encrypt(message, []*x509.Certificate{keypair.Leaf}) + if err != nil { + t.Error(err) + } + + 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) { - for _, keypair := range keyPairsOpenssl { - message := []byte("Hallo Welt") + if skipOpenssl { + return + } - sig, err := openssl.SignDetached(message, keypair.Leaf, keypair.PrivateKey, []*x509.Certificate{intermediate.Certificate}, "-outform", "DER") - if err != nil { - t.Error(err) - } + 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}, opensslSignOpts...) + 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) { - for _, keypair := range keyPairsOpenssl { - cms, err := New(keypair) - if err != nil { - t.Error(err) - } + if skipOpenssl { + return + } - cms.TimeStamp = true + 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/pssoaep.go b/cms/protocol/pssoaep.go new file mode 100644 index 0000000..273ccc7 --- /dev/null +++ b/cms/protocol/pssoaep.go @@ -0,0 +1,183 @@ +package protocol + +import ( + "crypto" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + + oid "github.com/InfiniteLoopSpace/go_S-MIME/oid" +) + +type pssParameters struct { + Hash pkix.AlgorithmIdentifier `asn1:"optional,explicit,tag:0"` + MGF pkix.AlgorithmIdentifier `asn1:"optional,explicit,tag:1"` + SaltLength int `asn1:"optional,explicit,tag:2"` + TrailerField int `asn1:"optional,explicit,tag:3"` //,default:1"` +} + +var oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} + +func newpssParameters(hash ...crypto.Hash) (param pssParameters, err error) { + + // SHA1 is default value + if len(hash) == 1 && hash[0] == crypto.SHA1 { + return + } + + var h asn1.ObjectIdentifier + + if len(hash) == 0 { + h = oid.DigestAlgorithmSHA1 + param.SaltLength = 20 + param.TrailerField = 1 + } else { + var ok bool + h, ok = oid.HashToDigestAlgorithm[hash[0]] + if !ok { + err = errors.New("Unsupported hashfunction") + } + } + + hRV, err := RawValue(pkix.AlgorithmIdentifier{Algorithm: h}) + if err != nil { + return + } + + param.Hash = pkix.AlgorithmIdentifier{Algorithm: h} + + param.MGF = pkix.AlgorithmIdentifier{Algorithm: oidMGF1, Parameters: hRV} + + return +} + +// This is needed because CheckSignature uses PSSSaltLengthEqualsHash, if PSSSaltLengthAuto is used this code is not needed +func verfiyRSAPSS(cert x509.Certificate, signatureAlgorithm pkix.AlgorithmIdentifier, signedMessage, signature []byte) (err error) { + + param, err := newpssParameters() + if err != nil { + return + } + + _, err = asn1.Unmarshal(signatureAlgorithm.Parameters.FullBytes, ¶m) + if err != nil { + return + } + + if !param.MGF.Algorithm.Equal(oidMGF1) { + err = errors.New("Mask generator funktion not supported; only MGF1 is supported") + return + } + + hash := oid.DigestAlgorithmToHash[param.Hash.Algorithm.String()] + + pssOpts := rsa.PSSOptions{SaltLength: param.SaltLength, Hash: hash} + + h := hash.New() + h.Write(signedMessage) + digest := h.Sum(nil) + + err = rsa.VerifyPSS(cert.PublicKey.(*rsa.PublicKey), hash, digest, signature, &pssOpts) + + return +} + +func newPSS(hash crypto.Hash, pub *rsa.PublicKey) (signatureAlgorithm pkix.AlgorithmIdentifier, opts *rsa.PSSOptions, err error) { + + opts = &rsa.PSSOptions{Hash: hash} + + pssParam, err := newpssParameters(hash) + if err != nil { + return + } + + pssParam.SaltLength = (pub.N.BitLen()+7)/8 - 2 - hash.Size() // https://golang.org/src/crypto/rsa/pss.go?s=6982:7095#L239 + + paramRV, err := RawValue(pssParam) + if err != nil { + return + } + signatureAlgorithm = pkix.AlgorithmIdentifier{Algorithm: oid.SignatureAlgorithmRSASSAPSS, Parameters: paramRV} + return +} + +// RSAESOAEPparams ::= SEQUENCE { +// hashFunc [0] AlgorithmIdentifier DEFAULT sha1Identifier, +// maskGenFunc [1] AlgorithmIdentifier DEFAULT mgf1SHA1Identifier, +// pSourceFunc [2] AlgorithmIdentifier DEFAULT +// pSpecifiedEmptyIdentifier } +type RSAESOAEPparams struct { + HashFunc pkix.AlgorithmIdentifier `asn1:"optional,explicit,tag:0"` + MaskGenFunc pkix.AlgorithmIdentifier `asn1:"optional,explicit,tag:1"` + PSourceFunc pkix.AlgorithmIdentifier `asn1:"optional,explicit,tag:2"` +} + +var oidpSpecified = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 9} + +func newRSAESOAEPparams(hash ...crypto.Hash) (param RSAESOAEPparams, err error) { + + // SHA1 is default value + if len(hash) == 1 && hash[0] == crypto.SHA1 { + return + } + + nullOctetString, err := RawValue([]byte{}) + + var h asn1.ObjectIdentifier + + if len(hash) == 0 { + h = oid.DigestAlgorithmSHA1 + } else { + var ok bool + h, ok = oid.HashToDigestAlgorithm[hash[0]] + if !ok { + err = errors.New("Unsupported hashfunction") + } + } + + hRV, err := RawValue(pkix.AlgorithmIdentifier{Algorithm: h}) + + param = RSAESOAEPparams{pkix.AlgorithmIdentifier{Algorithm: h, Parameters: asn1.NullRawValue}, + pkix.AlgorithmIdentifier{Algorithm: oidMGF1, Parameters: hRV}, + pkix.AlgorithmIdentifier{Algorithm: oidpSpecified, Parameters: nullOctetString}} + + return +} + +func parseRSAESOAEPparams(param []byte) (opts *rsa.OAEPOptions, err error) { + var oaepOpts RSAESOAEPparams + oaepOpts, err = newRSAESOAEPparams() + if err != nil { + return + } + + _, err = asn1.Unmarshal(param, &oaepOpts) + if err != nil { + return + } + + opts = &rsa.OAEPOptions{Hash: oid.DigestAlgorithmToHash[oaepOpts.HashFunc.Algorithm.String()], Label: []byte{}} + + if !oaepOpts.MaskGenFunc.Algorithm.Equal(oidMGF1) { + err = errors.New("Unsupported mask generation funktion" + oaepOpts.MaskGenFunc.Algorithm.String()) + return + } + + if !oaepOpts.PSourceFunc.Algorithm.Equal(oidpSpecified) { + err = errors.New("Unsupported p source funktion" + oaepOpts.PSourceFunc.Algorithm.String()) + return + } + + return +} + +func isRSAPSS(cert *x509.Certificate) bool { + switch cert.SignatureAlgorithm { + case x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS: + return true + default: + return false + } +} diff --git a/cms/protocol/reciepientinfo.go b/cms/protocol/reciepientinfo.go index 0956f46..50c8e6f 100644 --- a/cms/protocol/reciepientinfo.go +++ b/cms/protocol/reciepientinfo.go @@ -10,7 +10,6 @@ import ( "crypto/x509/pkix" "encoding/asn1" "errors" - "fmt" "log" "time" @@ -34,7 +33,7 @@ type RecipientInfo struct { func (recInfo *RecipientInfo) decryptKey(keyPair tls.Certificate) (key []byte, err error) { key, err = recInfo.KTRI.decryptKey(keyPair) - if key != nil { + if key != nil || (err != nil && err != ErrUnsupported) { return } @@ -64,6 +63,23 @@ func (ktri *KeyTransRecipientInfo) decryptKey(keyPair tls.Certificate) (key []by ski := keyPair.Leaf.SubjectKeyId + certPubAlg := oid.PublicKeyAlgorithmToEncrytionAlgorithm[keyPair.Leaf.PublicKeyAlgorithm].Algorithm + + var decOpts crypto.DecrypterOpts + pkcs15CertwithOAEP := false + + if ktri.KeyEncryptionAlgorithm.Algorithm.Equal(oid.EncryptionAlgorithmRSAESOAEP) { + + if certPubAlg.Equal(oid.EncryptionAlgorithmRSA) { + pkcs15CertwithOAEP = true + } + + decOpts, err = parseRSAESOAEPparams(ktri.KeyEncryptionAlgorithm.Parameters.FullBytes) + if err != nil { + return + } + } + //version is the syntax version number. If the SignerIdentifier is //the CHOICE issuerAndSerialNumber, then the version MUST be 1. If //the SignerIdentifier is subjectKeyIdentifier, then the version @@ -71,28 +87,25 @@ func (ktri *KeyTransRecipientInfo) decryptKey(keyPair tls.Certificate) (key []by switch ktri.Version { case 0: if ias.Equal(ktri.Rid.IAS) { - alg := oid.PublicKeyAlgorithmToEncrytionAlgorithm[keyPair.Leaf.PublicKeyAlgorithm].Algorithm - if ktri.KeyEncryptionAlgorithm.Algorithm.Equal(alg) { + if ktri.KeyEncryptionAlgorithm.Algorithm.Equal(certPubAlg) || pkcs15CertwithOAEP { decrypter := keyPair.PrivateKey.(crypto.Decrypter) - return decrypter.Decrypt(rand.Reader, ktri.EncryptedKey, nil) + return decrypter.Decrypt(rand.Reader, ktri.EncryptedKey, decOpts) } log.Println("Key encrytion algorithm not matching") } case 2: if bytes.Equal(ski, ktri.Rid.SKI) { - alg := oid.PublicKeyAlgorithmToEncrytionAlgorithm[keyPair.Leaf.PublicKeyAlgorithm].Algorithm - if ktri.KeyEncryptionAlgorithm.Algorithm.Equal(alg) { - if alg.Equal(oid.EncryptionAlgorithmRSA) { - return rsa.DecryptPKCS1v15(rand.Reader, keyPair.PrivateKey.(*rsa.PrivateKey), ktri.EncryptedKey) - } - log.Println("Unsupported key encrytion algorithm") + if ktri.KeyEncryptionAlgorithm.Algorithm.Equal(certPubAlg) || pkcs15CertwithOAEP { + + decrypter := keyPair.PrivateKey.(crypto.Decrypter) + return decrypter.Decrypt(rand.Reader, ktri.EncryptedKey, decOpts) + } log.Println("Key encrytion algorithm not matching") } default: - fmt.Println(ktri.Version) return nil, ErrUnsupported } @@ -109,35 +122,15 @@ type RecipientIdentifier struct { // NewRecipientInfo creates RecipientInfo for giben recipient and key. func NewRecipientInfo(recipient *x509.Certificate, key []byte) (info RecipientInfo, err error) { - version := 0 //issuerAndSerialNumber - - rid := RecipientIdentifier{} - - switch version { - case 0: - ias, err := NewIssuerAndSerialNumber(recipient) - if err != nil { - log.Fatal(err) - } - rid.IAS = ias - case 2: - rid.SKI = recipient.SubjectKeyId - } switch recipient.PublicKeyAlgorithm { case x509.RSA: - var encrypted []byte - encrypted, err = encryptKeyRSA(key, recipient) + var ktri KeyTransRecipientInfo + ktri, err = encryptKeyRSA(key, recipient) if err != nil { return } - info = RecipientInfo{ - KTRI: KeyTransRecipientInfo{ - Version: version, - Rid: rid, - KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oid.EncryptionAlgorithmRSA}, - EncryptedKey: encrypted, - }} + info = RecipientInfo{KTRI: ktri} case x509.ECDSA: var kari KeyAgreeRecipientInfo kari, err = encryptKeyECDH(key, recipient) @@ -152,11 +145,47 @@ func NewRecipientInfo(recipient *x509.Certificate, key []byte) (info RecipientIn return } -func encryptKeyRSA(key []byte, recipient *x509.Certificate) ([]byte, error) { - if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil { - return rsa.EncryptPKCS1v15(rand.Reader, pub, key) +func encryptKeyRSA(key []byte, recipient *x509.Certificate) (ktri KeyTransRecipientInfo, err error) { + ktri.Version = 0 //issuerAndSerialNumber + + switch ktri.Version { + case 0: + ias, err := NewIssuerAndSerialNumber(recipient) + if err != nil { + log.Fatal(err) + } + ktri.Rid.IAS = ias + case 2: + ktri.Rid.SKI = recipient.SubjectKeyId } - return nil, ErrUnsupportedAlgorithm + + if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil { + + if isRSAPSS(recipient) { + hash := crypto.SHA256 + var oaepparam RSAESOAEPparams + oaepparam, err = newRSAESOAEPparams(hash) + if err != nil { + return + } + var oaepparamRV asn1.RawValue + oaepparamRV, err = RawValue(oaepparam) + if err != nil { + return + } + ktri.KeyEncryptionAlgorithm = pkix.AlgorithmIdentifier{Algorithm: oid.EncryptionAlgorithmRSAESOAEP, Parameters: oaepparamRV} + h := hash.New() + ktri.EncryptedKey, err = rsa.EncryptOAEP(h, rand.Reader, pub, key, nil) + return + } + + ktri.KeyEncryptionAlgorithm = pkix.AlgorithmIdentifier{Algorithm: oid.EncryptionAlgorithmRSA} + ktri.EncryptedKey, err = rsa.EncryptPKCS1v15(rand.Reader, pub, key) + return + } + + err = ErrUnsupportedAlgorithm + return } // ErrUnsupportedAlgorithm is returned if the algorithm is unsupported. diff --git a/cms/protocol/signeddata.go b/cms/protocol/signeddata.go index 8db4583..4feebc7 100644 --- a/cms/protocol/signeddata.go +++ b/cms/protocol/signeddata.go @@ -6,6 +6,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" @@ -106,7 +107,7 @@ func NewSignedData(eci EncapsulatedContentInfo) (*SignedData, error) { } // AddSignerInfo adds a SignerInfo to the SignedData. -func (sd *SignedData) AddSignerInfo(keypPair tls.Certificate) (err error) { +func (sd *SignedData) AddSignerInfo(keypPair tls.Certificate, attrs []Attribute) (err error) { for _, cert := range keypPair.Certificate { if err = sd.AddCertificate(cert); err != nil { @@ -125,8 +126,13 @@ func (sd *SignedData) AddSignerInfo(keypPair tls.Certificate) (err error) { sid := SignerIdentifier{ias, nil} + var signerOpts crypto.SignerOpts digestAlgorithm := digestAlgorithmForPublicKey(cert.PublicKey) signatureAlgorithm, ok := oid.PublicKeyAlgorithmToSignatureAlgorithm[keypPair.Leaf.PublicKeyAlgorithm] + if isRSAPSS(cert) { + h := oid.DigestAlgorithmToHash[digestAlgorithm.Algorithm.String()] + signatureAlgorithm, signerOpts, err = newPSS(h, cert.PublicKey.(*rsa.PublicKey)) + } if !ok { return errors.New("unsupported certificate public key algorithm") } @@ -155,6 +161,11 @@ func (sd *SignedData) AddSignerInfo(keypPair tls.Certificate) (err error) { if err != nil { return err } + + if !isRSAPSS(cert) { + signerOpts = hash + } + md := hash.New() if _, err = md.Write(content); err != nil { return err @@ -174,6 +185,7 @@ func (sd *SignedData) AddSignerInfo(keypPair tls.Certificate) (err error) { return err } si.SignedAttrs = append(si.SignedAttrs, mdAttr, ctAttr, sTAttr) + si.SignedAttrs = append(si.SignedAttrs, attrs...) sm, err := asn.MarshalWithParams(si.SignedAttrs, `set`) if err != nil { @@ -184,7 +196,7 @@ func (sd *SignedData) AddSignerInfo(keypPair tls.Certificate) (err error) { if _, errr := smd.Write(sm); errr != nil { return errr } - if si.Signature, err = signer.Sign(rand.Reader, smd.Sum(nil), hash); err != nil { + if si.Signature, err = signer.Sign(rand.Reader, smd.Sum(nil), signerOpts); err != nil { return err } @@ -384,7 +396,11 @@ func (sd *SignedData) Verify(Opts x509.VerifyOptions, detached []byte) (chains [ if err != nil { return } - err = cert.CheckSignature(sigAlg, signedMessage, signer.Signature) + switch signer.SignatureAlgorithm.Algorithm.String() { + case oid.SignatureAlgorithmRSASSAPSS.String(): + default: + err = cert.CheckSignature(sigAlg, signedMessage, signer.Signature) + } if err != nil { return } diff --git a/oid/oid.go b/oid/oid.go index e58ec68..c61e872 100644 --- a/oid/oid.go +++ b/oid/oid.go @@ -29,6 +29,7 @@ var ( // Signature Algorithm OIDs var ( SignatureAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + SignatureAlgorithmRSASSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} 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} @@ -39,7 +40,8 @@ var ( // Public Key Encryption OIDs var ( - EncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + EncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + EncryptionAlgorithmRSAESOAEP = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 7} ) // Digest Algorithm OIDs @@ -133,6 +135,11 @@ var SignatureAlgorithms = map[string]map[string]x509.SignatureAlgorithm{ DigestAlgorithmSHA384.String(): x509.SHA384WithRSA, DigestAlgorithmSHA512.String(): x509.SHA512WithRSA, }, + SignatureAlgorithmRSASSAPSS.String(): map[string]x509.SignatureAlgorithm{ + DigestAlgorithmSHA256.String(): x509.SHA256WithRSAPSS, + DigestAlgorithmSHA384.String(): x509.SHA384WithRSAPSS, + DigestAlgorithmSHA512.String(): x509.SHA512WithRSAPSS, + }, SignatureAlgorithmECDSA.String(): map[string]x509.SignatureAlgorithm{ DigestAlgorithmSHA1.String(): x509.ECDSAWithSHA1, DigestAlgorithmSHA256.String(): x509.ECDSAWithSHA256, diff --git a/openssl/openssl.go b/openssl/openssl.go index 1f2a239..7e4f3da 100644 --- a/openssl/openssl.go +++ b/openssl/openssl.go @@ -25,8 +25,15 @@ func Encrypt(in []byte, cert *x509.Certificate, opts ...string) (der []byte, err pem.Encode(tmpKey, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) param := []string{SMIME, "-encrypt", "-aes128"} - param = append(param, opts...) - param = append(param, tmpKey.Name()) + if SMIME == "smime" { + // For smime arguments can not be passed after the keyfile + param = append(param, opts...) + param = append(param, tmpKey.Name()) + } else { + // Keyots have to be passed after the key + param = append(param, "-recip", tmpKey.Name()) + param = append(param, opts...) + } der, err = openssl(in, param...) return @@ -76,9 +83,8 @@ 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", "-signer", tmpCert.Name(), "-inkey", tmpKey.Name(), "-certfile", tmpInterm.Name()} param = append(param, opts...) - param = append(param, []string{"-signer", tmpCert.Name(), "-inkey", tmpKey.Name(), "-certfile", tmpInterm.Name()}...) plain, err = openssl(in, param...) return diff --git a/pki/pki.go b/pki/pki.go index 88a597c..a103231 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -22,6 +22,7 @@ type configuration struct { subject *pkix.Name issuer *Identity nextSN *int64 + signatureAlgrotim x509.SignatureAlgorithm priv *crypto.Signer isCA bool notBefore *time.Time @@ -33,6 +34,7 @@ type configuration struct { func (c *configuration) generate() *Identity { templ := &x509.Certificate{ Subject: c.getSubject(), + SignatureAlgorithm: c.signatureAlgrotim, IsCA: c.isCA, BasicConstraintsValid: true, NotAfter: c.getNotAfter(), @@ -195,6 +197,13 @@ func PrivateKey(value crypto.Signer) Option { } } +// SignatureAlgorithm is an Option for setting the signature algorithm. +func SignatureAlgorithm(value x509.SignatureAlgorithm) Option { + return func(c *configuration) { + c.signatureAlgrotim = value + } +} + // Issuer is an Option for setting the identity's issuer. func Issuer(value *Identity) Option { return func(c *configuration) { diff --git a/smime/smime.go b/smime/smime.go index 80cc631..bdc96bf 100644 --- a/smime/smime.go +++ b/smime/smime.go @@ -6,11 +6,15 @@ package smime import ( "crypto/tls" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/base64" "errors" "log" "strings" + "github.com/InfiniteLoopSpace/go_S-MIME/oid" + "github.com/InfiniteLoopSpace/go_S-MIME/b64" cms "github.com/InfiniteLoopSpace/go_S-MIME/cms" @@ -23,6 +27,26 @@ type SMIME struct { CMS *cms.CMS } +var oidsmimeCapabilities = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 15} + +//SMIMECapability ::= SEQUENCE { +//capabilityID OBJECT IDENTIFIER, +//parameters ANY DEFINED BY capabilityID OPTIONAL } + +//SMIMECapabilities ::= SEQUENCE OF SMIMECapability + +func (smime *SMIME) addSMIMECapabilitesAttr() (err error) { + + var smimeCapabilities []pkix.AlgorithmIdentifier + + smimeCapabilities = append(smimeCapabilities, pkix.AlgorithmIdentifier{Algorithm: oid.EncryptionAlgorithmAES128CBC}) + smimeCapabilities = append(smimeCapabilities, pkix.AlgorithmIdentifier{Algorithm: oid.AEADChaCha20Poly1305}) + + err = smime.CMS.AddAttribute(oidsmimeCapabilities, smimeCapabilities) + + return +} + // New create a new instance of SMIME with given keyPairs. func New(keyPair ...tls.Certificate) (smime *SMIME, err error) { CMS, err := cms.New(keyPair...) @@ -31,6 +55,10 @@ func New(keyPair ...tls.Certificate) (smime *SMIME, err error) { } smime = &SMIME{CMS} + err = smime.addSMIMECapabilitesAttr() + if err != nil { + return + } return }