Skip to content

Commit f777964

Browse files
Merge pull request #1 from UniNow/gcm
feat: gcm
2 parents 29c6295 + 1290e1a commit f777964

13 files changed

Lines changed: 668 additions & 104 deletions

identity_provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ func (req *IdpAuthnRequest) MakeAssertionEl() error {
854854
encryptor := xmlenc.OAEP()
855855
encryptor.BlockCipher = xmlenc.AES128CBC
856856
encryptor.DigestMethod = &xmlenc.SHA1
857-
encryptedDataEl, err := encryptor.Encrypt(certBuf, signedAssertionBuf)
857+
encryptedDataEl, err := encryptor.Encrypt(certBuf, signedAssertionBuf, nil)
858858
if err != nil {
859859
return err
860860
}

xmlenc/cbc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (e CBC) Algorithm() string {
3131

3232
// Encrypt encrypts plaintext with key, which should be a []byte of length KeySize().
3333
// It returns an xenc:EncryptedData element.
34-
func (e CBC) Encrypt(key interface{}, plaintext []byte) (*etree.Element, error) {
34+
func (e CBC) Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) {
3535
keyBuf, ok := key.([]byte)
3636
if !ok {
3737
return nil, ErrIncorrectKeyType("[]byte")

xmlenc/decrypt_test.go

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,93 @@ import (
1313
)
1414

1515
func TestCanDecrypt(t *testing.T) {
16-
doc := etree.NewDocument()
17-
err := doc.ReadFromBytes(golden.Get(t, "input.xml"))
18-
assert.Check(t, err)
19-
20-
keyPEM := "-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDU8wdiaFmPfTyRYuFlVPi866WrH/2JubkHzp89bBQopDaLXYxi\n3PTu3O6Q/KaKxMOFBqrInwqpv/omOGZ4ycQ51O9I+Yc7ybVlW94lTo2gpGf+Y/8E\nPsVbnZaFutRctJ4dVIp9aQ2TpLiGT0xX1OzBO/JEgq9GzDRf+B+eqSuglwIDAQAB\nAoGBAMuy1eN6cgFiCOgBsB3gVDdTKpww87Qk5ivjqEt28SmXO13A1KNVPS6oQ8SJ\nCT5Azc6X/BIAoJCURVL+LHdqebogKljhH/3yIel1kH19vr4E2kTM/tYH+qj8afUS\nJEmArUzsmmK8ccuNqBcllqdwCZjxL4CHDUmyRudFcHVX9oyhAkEA/OV1OkjM3CLU\nN3sqELdMmHq5QZCUihBmk3/N5OvGdqAFGBlEeewlepEVxkh7JnaNXAXrKHRVu/f/\nfbCQxH+qrwJBANeQERF97b9Sibp9xgolb749UWNlAdqmEpmlvmS202TdcaaT1msU\n4rRLiQN3X9O9mq4LZMSVethrQAdX1whawpkCQQDk1yGf7xZpMJ8F4U5sN+F4rLyM\nRq8Sy8p2OBTwzCUXXK+fYeXjybsUUMr6VMYTRP2fQr/LKJIX+E5ZxvcIyFmDAkEA\nyfjNVUNVaIbQTzEbRlRvT6MqR+PTCefC072NF9aJWR93JimspGZMR7viY6IM4lrr\nvBkm0F5yXKaYtoiiDMzlOQJADqmEwXl0D72ZG/2KDg8b4QZEmC9i5gidpQwJXUc6\nhU+IVQoLxRq0fBib/36K9tcrrO5Ba4iEvDcNY+D8yGbUtA==\n-----END RSA PRIVATE KEY-----\n"
21-
b, _ := pem.Decode([]byte(keyPEM))
22-
key, err := x509.ParsePKCS1PrivateKey(b.Bytes)
23-
assert.Check(t, err)
24-
25-
el := doc.Root().FindElement("//EncryptedKey")
26-
buf, err := Decrypt(key, el)
27-
assert.Check(t, err)
28-
assert.Check(t, is.DeepEqual([]byte{0xc, 0x70, 0xa2, 0xc8, 0x15, 0x74, 0x89, 0x3f, 0x36, 0xd2, 0x7c, 0x14, 0x2a, 0x9b, 0xaa, 0xd9},
29-
buf))
30-
31-
el = doc.Root().FindElement("//EncryptedData")
32-
buf, err = Decrypt(key, el)
33-
assert.Check(t, err)
34-
golden.Assert(t, string(buf), "plaintext.xml")
16+
t.Run("CBC", func(t *testing.T) {
17+
doc := etree.NewDocument()
18+
err := doc.ReadFromBytes(golden.Get(t, "input.xml"))
19+
assert.Check(t, err)
20+
21+
keyPEM := "-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDU8wdiaFmPfTyRYuFlVPi866WrH/2JubkHzp89bBQopDaLXYxi\n3PTu3O6Q/KaKxMOFBqrInwqpv/omOGZ4ycQ51O9I+Yc7ybVlW94lTo2gpGf+Y/8E\nPsVbnZaFutRctJ4dVIp9aQ2TpLiGT0xX1OzBO/JEgq9GzDRf+B+eqSuglwIDAQAB\nAoGBAMuy1eN6cgFiCOgBsB3gVDdTKpww87Qk5ivjqEt28SmXO13A1KNVPS6oQ8SJ\nCT5Azc6X/BIAoJCURVL+LHdqebogKljhH/3yIel1kH19vr4E2kTM/tYH+qj8afUS\nJEmArUzsmmK8ccuNqBcllqdwCZjxL4CHDUmyRudFcHVX9oyhAkEA/OV1OkjM3CLU\nN3sqELdMmHq5QZCUihBmk3/N5OvGdqAFGBlEeewlepEVxkh7JnaNXAXrKHRVu/f/\nfbCQxH+qrwJBANeQERF97b9Sibp9xgolb749UWNlAdqmEpmlvmS202TdcaaT1msU\n4rRLiQN3X9O9mq4LZMSVethrQAdX1whawpkCQQDk1yGf7xZpMJ8F4U5sN+F4rLyM\nRq8Sy8p2OBTwzCUXXK+fYeXjybsUUMr6VMYTRP2fQr/LKJIX+E5ZxvcIyFmDAkEA\nyfjNVUNVaIbQTzEbRlRvT6MqR+PTCefC072NF9aJWR93JimspGZMR7viY6IM4lrr\nvBkm0F5yXKaYtoiiDMzlOQJADqmEwXl0D72ZG/2KDg8b4QZEmC9i5gidpQwJXUc6\nhU+IVQoLxRq0fBib/36K9tcrrO5Ba4iEvDcNY+D8yGbUtA==\n-----END RSA PRIVATE KEY-----\n"
22+
b, _ := pem.Decode([]byte(keyPEM))
23+
key, err := x509.ParsePKCS1PrivateKey(b.Bytes)
24+
assert.Check(t, err)
25+
26+
el := doc.Root().FindElement("//EncryptedKey")
27+
buf, err := Decrypt(key, el)
28+
assert.Check(t, err)
29+
assert.Check(t, is.DeepEqual([]byte{0xc, 0x70, 0xa2, 0xc8, 0x15, 0x74, 0x89, 0x3f, 0x36, 0xd2, 0x7c, 0x14, 0x2a, 0x9b, 0xaa, 0xd9},
30+
buf))
31+
32+
el = doc.Root().FindElement("//EncryptedData")
33+
buf, err = Decrypt(key, el)
34+
assert.Check(t, err)
35+
golden.Assert(t, string(buf), "plaintext.xml")
36+
})
37+
38+
t.Run("GCM", func(t *testing.T) {
39+
doc := etree.NewDocument()
40+
err := doc.ReadFromBytes(golden.Get(t, "input_gcm.xml"))
41+
assert.Check(t, err)
42+
43+
keyPEM := golden.Get(t, "cert.key")
44+
b, _ := pem.Decode(keyPEM)
45+
key, err := x509.ParsePKCS8PrivateKey(b.Bytes)
46+
assert.Check(t, err)
47+
48+
el := doc.Root().FindElement("//EncryptedKey")
49+
_, err = Decrypt(key, el)
50+
assert.Check(t, err)
51+
52+
el = doc.Root().FindElement("//EncryptedData")
53+
_, err = Decrypt(key, el)
54+
assert.Check(t, err)
55+
})
3556
}
3657

3758
func TestCanDecryptWithoutCertificate(t *testing.T) {
38-
doc := etree.NewDocument()
39-
err := doc.ReadFromBytes(golden.Get(t, "input.xml"))
40-
assert.Check(t, err)
41-
42-
el := doc.FindElement("//ds:X509Certificate")
43-
el.Parent().RemoveChild(el)
44-
45-
keyPEM := golden.Get(t, "key.pem")
46-
b, _ := pem.Decode(keyPEM)
47-
key, err := x509.ParsePKCS1PrivateKey(b.Bytes)
48-
assert.Check(t, err)
49-
50-
el = doc.Root().FindElement("//EncryptedKey")
51-
buf, err := Decrypt(key, el)
52-
assert.Check(t, err)
53-
assert.Check(t, is.DeepEqual([]byte{0xc, 0x70, 0xa2, 0xc8, 0x15, 0x74, 0x89, 0x3f, 0x36, 0xd2, 0x7c, 0x14, 0x2a, 0x9b, 0xaa, 0xd9}, buf))
54-
55-
el = doc.Root().FindElement("//EncryptedData")
56-
buf, err = Decrypt(key, el)
57-
assert.Check(t, err)
58-
golden.Assert(t, string(buf), "plaintext.xml")
59+
t.Run("CBC", func(t *testing.T) {
60+
doc := etree.NewDocument()
61+
err := doc.ReadFromBytes(golden.Get(t, "input.xml"))
62+
assert.Check(t, err)
63+
64+
el := doc.FindElement("//ds:X509Certificate")
65+
el.Parent().RemoveChild(el)
66+
67+
keyPEM := golden.Get(t, "key.pem")
68+
b, _ := pem.Decode(keyPEM)
69+
key, err := x509.ParsePKCS1PrivateKey(b.Bytes)
70+
assert.Check(t, err)
71+
72+
el = doc.Root().FindElement("//EncryptedKey")
73+
buf, err := Decrypt(key, el)
74+
assert.Check(t, err)
75+
assert.Check(t, is.DeepEqual([]byte{0xc, 0x70, 0xa2, 0xc8, 0x15, 0x74, 0x89, 0x3f, 0x36, 0xd2, 0x7c, 0x14, 0x2a, 0x9b, 0xaa, 0xd9}, buf))
76+
77+
el = doc.Root().FindElement("//EncryptedData")
78+
buf, err = Decrypt(key, el)
79+
assert.Check(t, err)
80+
golden.Assert(t, string(buf), "plaintext.xml")
81+
})
82+
83+
t.Run("GCM", func(t *testing.T) {
84+
doc := etree.NewDocument()
85+
err := doc.ReadFromBytes(golden.Get(t, "input_gcm.xml"))
86+
assert.Check(t, err)
87+
88+
el := doc.FindElement("//ds:X509Certificate")
89+
el.Parent().RemoveChild(el)
90+
91+
keyPEM := golden.Get(t, "cert.key")
92+
b, _ := pem.Decode(keyPEM)
93+
key, err := x509.ParsePKCS8PrivateKey(b.Bytes)
94+
assert.Check(t, err)
95+
96+
el = doc.Root().FindElement("//EncryptedKey")
97+
_, err = Decrypt(key, el)
98+
assert.Check(t, err)
99+
100+
el = doc.Root().FindElement("//EncryptedData")
101+
_, err = Decrypt(key, el)
102+
assert.Check(t, err)
103+
//assertion.NotNil(t, plaintext)
104+
})
59105
}

xmlenc/encrypt_test.go

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,56 @@ package xmlenc
33
import (
44
"crypto/x509"
55
"encoding/pem"
6-
"math/rand"
7-
"testing"
8-
6+
"github.com/beevik/etree"
97
"gotest.tools/assert"
108
"gotest.tools/golden"
11-
12-
"github.com/beevik/etree"
9+
"math/rand"
10+
"testing"
1311
)
1412

1513
func TestCanEncryptOAEP(t *testing.T) {
16-
RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests
14+
t.Run("CBC", func(t *testing.T) {
15+
16+
RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests
17+
18+
pemBlock, _ := pem.Decode(golden.Get(t, "cert.pem"))
19+
certificate, err := x509.ParseCertificate(pemBlock.Bytes)
20+
assert.Check(t, err)
21+
22+
e := OAEP()
23+
e.BlockCipher = AES128CBC
24+
e.DigestMethod = &SHA1
25+
26+
el, err := e.Encrypt(certificate, golden.Get(t, "plaintext.xml"), nil)
27+
assert.Check(t, err)
28+
29+
doc := etree.NewDocument()
30+
doc.SetRoot(el)
31+
doc.IndentTabs()
32+
ciphertext, _ := doc.WriteToString()
33+
34+
golden.Assert(t, ciphertext, "ciphertext.xml")
35+
})
1736

18-
pemBlock, _ := pem.Decode(golden.Get(t, "cert.pem"))
19-
certificate, err := x509.ParseCertificate(pemBlock.Bytes)
20-
assert.Check(t, err)
37+
t.Run("GCM", func(t *testing.T) {
38+
RandReader = rand.New(rand.NewSource(0)) //nolint:gosec // deterministic random numbers for tests
2139

22-
e := OAEP()
23-
e.BlockCipher = AES128CBC
24-
e.DigestMethod = &SHA1
40+
cert := golden.Get(t, "cert.cert")
41+
b, _ := pem.Decode(cert)
42+
certificate, err := x509.ParseCertificate(b.Bytes)
43+
assert.Check(t, err)
2544

26-
el, err := e.Encrypt(certificate, golden.Get(t, "plaintext.xml"))
27-
assert.Check(t, err)
45+
e := OAEP()
46+
e.BlockCipher = AES128GCM
47+
e.DigestMethod = &SHA1
2848

29-
doc := etree.NewDocument()
30-
doc.SetRoot(el)
31-
doc.IndentTabs()
32-
ciphertext, _ := doc.WriteToString()
49+
el, err := e.Encrypt(certificate, golden.Get(t, "plaintext_gcm.xml"), []byte("1234567890AZ"))
50+
assert.Check(t, err)
3351

34-
golden.Assert(t, ciphertext, "ciphertext.xml")
52+
doc := etree.NewDocument()
53+
doc.SetRoot(el)
54+
doc.Indent(4)
55+
ciphertext, _ := doc.WriteToString()
56+
golden.Assert(t, ciphertext, "ciphertext_gcm.xml")
57+
})
3558
}

xmlenc/gcm.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package xmlenc
2+
3+
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
"crypto/rand"
7+
"encoding/base64"
8+
"fmt"
9+
"github.com/beevik/etree"
10+
"io"
11+
)
12+
13+
// struct implements Decrypter and Encrypter for block ciphers in struct mode
14+
type GCM struct {
15+
keySize int
16+
algorithm string
17+
cipher func([]byte) (cipher.Block, error)
18+
}
19+
20+
// KeySize returns the length of the key required.
21+
func (e GCM) KeySize() int {
22+
return e.keySize
23+
}
24+
25+
// Algorithm returns the name of the algorithm, as will be found
26+
// in an xenc:EncryptionMethod element.
27+
func (e GCM) Algorithm() string {
28+
return e.algorithm
29+
}
30+
31+
func (e GCM) Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) {
32+
keyBuf, ok := key.([]byte)
33+
if !ok {
34+
return nil, ErrIncorrectKeyType("[]byte")
35+
}
36+
if len(keyBuf) != e.keySize {
37+
return nil, ErrIncorrectKeyLength(e.keySize)
38+
}
39+
40+
block, err := e.cipher(keyBuf)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
encryptedDataEl := etree.NewElement("xenc:EncryptedData")
46+
encryptedDataEl.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
47+
{
48+
randBuf := make([]byte, 16)
49+
if _, err := RandReader.Read(randBuf); err != nil {
50+
return nil, err
51+
}
52+
encryptedDataEl.CreateAttr("Id", fmt.Sprintf("_%x", randBuf))
53+
}
54+
55+
em := encryptedDataEl.CreateElement("xenc:EncryptionMethod")
56+
em.CreateAttr("Algorithm", e.algorithm)
57+
em.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
58+
59+
plaintext = appendPadding(plaintext, block.BlockSize())
60+
61+
aesgcm, err := cipher.NewGCM(block)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
if nonce == nil {
67+
// generate random nonce when it's nil
68+
nonce := make([]byte, aesgcm.NonceSize())
69+
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
70+
panic(err.Error())
71+
}
72+
}
73+
74+
ciphertext := make([]byte, len(plaintext))
75+
text := aesgcm.Seal(nil, nonce, ciphertext, nil)
76+
77+
cd := encryptedDataEl.CreateElement("xenc:CipherData")
78+
cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
79+
cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(text))
80+
return encryptedDataEl, nil
81+
}
82+
83+
// Decrypt decrypts an encrypted element with key. If the ciphertext contains an
84+
// EncryptedKey element, then the type of `key` is determined by the registered
85+
// Decryptor for the EncryptedKey element. Otherwise, `key` must be a []byte of
86+
// length KeySize().
87+
func (e GCM) Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) {
88+
if encryptedKeyEl := ciphertextEl.FindElement("./KeyInfo/EncryptedKey"); encryptedKeyEl != nil {
89+
var err error
90+
key, err = Decrypt(key, encryptedKeyEl)
91+
if err != nil {
92+
return nil, err
93+
}
94+
}
95+
96+
keyBuf, ok := key.([]byte)
97+
98+
if !ok {
99+
return nil, ErrIncorrectKeyType("[]byte")
100+
}
101+
if len(keyBuf) != e.KeySize() {
102+
return nil, ErrIncorrectKeyLength(e.KeySize())
103+
}
104+
105+
block, err := e.cipher(keyBuf)
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
aesgcm, err := cipher.NewGCM(block)
111+
if err != nil {
112+
return nil, err
113+
}
114+
115+
ciphertext, err := getCiphertext(ciphertextEl)
116+
if err != nil {
117+
return nil, err
118+
}
119+
120+
nonce := ciphertext[:aesgcm.NonceSize()]
121+
text := ciphertext[aesgcm.NonceSize():]
122+
123+
plainText, err := aesgcm.Open(nil, nonce, text, nil)
124+
if err != nil {
125+
return nil, err
126+
}
127+
return plainText, nil
128+
}
129+
130+
var (
131+
// AES128GCM implements AES128-GCM mode for encryption and decryption
132+
AES128GCM BlockCipher = GCM{
133+
keySize: 16,
134+
algorithm: "http://www.w3.org/2009/xmlenc11#aes128-gcm",
135+
cipher: aes.NewCipher,
136+
}
137+
)
138+
139+
func init() {
140+
RegisterDecrypter(AES128GCM)
141+
}

xmlenc/pubkey.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func (e RSA) Algorithm() string {
2929

3030
// Encrypt implements encrypter. certificate must be a []byte containing the ASN.1 bytes
3131
// of certificate containing an RSA public key.
32-
func (e RSA) Encrypt(certificate interface{}, plaintext []byte) (*etree.Element, error) {
32+
func (e RSA) Encrypt(certificate interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) {
3333
cert, ok := certificate.(*x509.Certificate)
3434
if !ok {
3535
return nil, ErrIncorrectKeyType("*x.509 certificate")
@@ -83,11 +83,11 @@ func (e RSA) Encrypt(certificate interface{}, plaintext []byte) (*etree.Element,
8383
cd := encryptedKey.CreateElement("xenc:CipherData")
8484
cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
8585
cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(buf))
86-
encryptedDataEl, err := e.BlockCipher.Encrypt(key, plaintext)
86+
encryptedDataEl, err := e.BlockCipher.Encrypt(key, plaintext, nonce)
8787
if err != nil {
8888
return nil, err
8989
}
90-
encryptedDataEl.InsertChild(encryptedDataEl.FindElement("./CipherData"), keyInfoEl)
90+
encryptedDataEl.InsertChildAt(encryptedDataEl.FindElement("./CipherData").Index(), keyInfoEl)
9191

9292
return encryptedDataEl, nil
9393
}

0 commit comments

Comments
 (0)