Initial QSfera import
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des" // nolint: gas
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// CBC implements Decrypter and Encrypter for block ciphers in CBC mode
|
||||
type CBC struct {
|
||||
keySize int
|
||||
algorithm string
|
||||
cipher func([]byte) (cipher.Block, error)
|
||||
}
|
||||
|
||||
// KeySize returns the length of the key required.
|
||||
func (e CBC) KeySize() int {
|
||||
return e.keySize
|
||||
}
|
||||
|
||||
// Algorithm returns the name of the algorithm, as will be found
|
||||
// in an xenc:EncryptionMethod element.
|
||||
func (e CBC) Algorithm() string {
|
||||
return e.algorithm
|
||||
}
|
||||
|
||||
// Encrypt encrypts plaintext with key, which should be a []byte of length KeySize().
|
||||
// It returns an xenc:EncryptedData element.
|
||||
func (e CBC) Encrypt(key interface{}, plaintext []byte, _ []byte) (*etree.Element, error) {
|
||||
keyBuf, ok := key.([]byte)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("[]byte")
|
||||
}
|
||||
if len(keyBuf) != e.keySize {
|
||||
return nil, ErrIncorrectKeyLength(e.keySize)
|
||||
}
|
||||
|
||||
block, err := e.cipher(keyBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encryptedDataEl := etree.NewElement("xenc:EncryptedData")
|
||||
encryptedDataEl.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
{
|
||||
randBuf := make([]byte, 16)
|
||||
if _, err := RandReader.Read(randBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedDataEl.CreateAttr("Id", fmt.Sprintf("_%x", randBuf))
|
||||
}
|
||||
|
||||
em := encryptedDataEl.CreateElement("xenc:EncryptionMethod")
|
||||
em.CreateAttr("Algorithm", e.algorithm)
|
||||
em.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
|
||||
plaintext = appendPadding(plaintext, block.BlockSize())
|
||||
|
||||
iv := make([]byte, block.BlockSize())
|
||||
if _, err := RandReader.Read(iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
mode.CryptBlocks(ciphertext, plaintext)
|
||||
ciphertext = append(iv, ciphertext...)
|
||||
|
||||
cd := encryptedDataEl.CreateElement("xenc:CipherData")
|
||||
cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(ciphertext))
|
||||
return encryptedDataEl, nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts an encrypted element with key. If the ciphertext contains an
|
||||
// EncryptedKey element, then the type of `key` is determined by the registered
|
||||
// Decryptor for the EncryptedKey element. Otherwise, `key` must be a []byte of
|
||||
// length KeySize().
|
||||
func (e CBC) Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) {
|
||||
// If the key is encrypted, decrypt it.
|
||||
if encryptedKeyEl := ciphertextEl.FindElement("./KeyInfo/EncryptedKey"); encryptedKeyEl != nil {
|
||||
var err error
|
||||
key, err = Decrypt(key, encryptedKeyEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
keyBuf, ok := key.([]byte)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("[]byte")
|
||||
}
|
||||
if len(keyBuf) != e.KeySize() {
|
||||
return nil, ErrIncorrectKeyLength(e.KeySize())
|
||||
}
|
||||
|
||||
block, err := e.cipher(keyBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext, err := getCiphertext(ciphertextEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ciphertext) < block.BlockSize() {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
ciphertext = ciphertext[aes.BlockSize:]
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
mode.CryptBlocks(plaintext, ciphertext) // decrypt in place
|
||||
|
||||
plaintext, err = stripPadding(plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// AES128CBC implements AES128-CBC symetric key mode for encryption and decryption
|
||||
AES128CBC BlockCipher = CBC{
|
||||
keySize: 16,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#aes128-cbc",
|
||||
cipher: aes.NewCipher,
|
||||
}
|
||||
|
||||
// AES192CBC implements AES192-CBC symetric key mode for encryption and decryption
|
||||
AES192CBC BlockCipher = CBC{
|
||||
keySize: 24,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#aes192-cbc",
|
||||
cipher: aes.NewCipher,
|
||||
}
|
||||
|
||||
// AES256CBC implements AES256-CBC symetric key mode for encryption and decryption
|
||||
AES256CBC BlockCipher = CBC{
|
||||
keySize: 32,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc",
|
||||
cipher: aes.NewCipher,
|
||||
}
|
||||
|
||||
// TripleDES implements 3DES in CBC mode for encryption and decryption
|
||||
TripleDES BlockCipher = CBC{
|
||||
keySize: 8,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
|
||||
cipher: des.NewCipher,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDecrypter(AES128CBC)
|
||||
RegisterDecrypter(AES192CBC)
|
||||
RegisterDecrypter(AES256CBC)
|
||||
RegisterDecrypter(TripleDES)
|
||||
}
|
||||
|
||||
func appendPadding(buf []byte, blockSize int) []byte {
|
||||
paddingBytes := blockSize - (len(buf) % blockSize)
|
||||
padding := make([]byte, paddingBytes)
|
||||
padding[len(padding)-1] = byte(paddingBytes)
|
||||
return append(buf, padding...)
|
||||
}
|
||||
|
||||
func stripPadding(buf []byte) ([]byte, error) {
|
||||
if len(buf) < 1 {
|
||||
return nil, errors.New("buffer is too short for padding")
|
||||
}
|
||||
paddingBytes := int(buf[len(buf)-1])
|
||||
if paddingBytes > len(buf)-1 {
|
||||
return nil, errors.New("buffer is too short for padding")
|
||||
}
|
||||
if paddingBytes < 1 {
|
||||
return nil, errors.New("padding must be at least one byte")
|
||||
}
|
||||
return buf[:len(buf)-paddingBytes], nil
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
|
||||
// nolint: gas
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// ErrAlgorithmNotImplemented is returned when encryption used is not
|
||||
// supported.
|
||||
type ErrAlgorithmNotImplemented string
|
||||
|
||||
func (e ErrAlgorithmNotImplemented) Error() string {
|
||||
return "algorithm is not implemented: " + string(e)
|
||||
}
|
||||
|
||||
// ErrCannotFindRequiredElement is returned by Decrypt when a required
|
||||
// element cannot be found.
|
||||
type ErrCannotFindRequiredElement string
|
||||
|
||||
func (e ErrCannotFindRequiredElement) Error() string {
|
||||
return "cannot find required element: " + string(e)
|
||||
}
|
||||
|
||||
// ErrIncorrectTag is returned when Decrypt is passed an element which
|
||||
// is neither an EncryptedType nor an EncryptedKey
|
||||
var ErrIncorrectTag = fmt.Errorf("tag must be an EncryptedType or EncryptedKey")
|
||||
|
||||
// ErrIncorrectKeyLength is returned when the fixed length key is not
|
||||
// of the required length.
|
||||
type ErrIncorrectKeyLength int
|
||||
|
||||
func (e ErrIncorrectKeyLength) Error() string {
|
||||
return fmt.Sprintf("expected key to be %d bytes", int(e))
|
||||
}
|
||||
|
||||
// ErrIncorrectKeyType is returned when the key is not the correct type
|
||||
type ErrIncorrectKeyType string
|
||||
|
||||
func (e ErrIncorrectKeyType) Error() string {
|
||||
return fmt.Sprintf("expected key to be %s", string(e))
|
||||
}
|
||||
|
||||
// Decrypt decrypts the encrypted data using the provided key. If the
|
||||
// data are encrypted using AES or 3DEC, then the key should be a []byte.
|
||||
// If the data are encrypted with PKCS1v15 or RSA-OAEP-MGF1P then key should
|
||||
// be a *rsa.PrivateKey.
|
||||
func Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) {
|
||||
encryptionMethodEl := ciphertextEl.FindElement("./EncryptionMethod")
|
||||
if encryptionMethodEl == nil {
|
||||
return nil, ErrCannotFindRequiredElement("EncryptionMethod")
|
||||
}
|
||||
algorithm := encryptionMethodEl.SelectAttrValue("Algorithm", "")
|
||||
decrypter, ok := decrypters[algorithm]
|
||||
if !ok {
|
||||
return nil, ErrAlgorithmNotImplemented(algorithm)
|
||||
}
|
||||
return decrypter.Decrypt(key, ciphertextEl)
|
||||
}
|
||||
|
||||
func getCiphertext(encryptedKey *etree.Element) ([]byte, error) {
|
||||
ciphertextEl := encryptedKey.FindElement("./CipherData/CipherValue")
|
||||
if ciphertextEl == nil {
|
||||
return nil, fmt.Errorf("cannot find CipherData element containing a CipherValue element")
|
||||
}
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(strings.TrimSpace(ciphertextEl.Text()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func validateRSAKeyIfPresent(key interface{}, encryptedKey *etree.Element) (*rsa.PrivateKey, error) {
|
||||
rsaKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("expected key to be a *rsa.PrivateKey")
|
||||
}
|
||||
|
||||
// extract and verify that the public key matches the certificate
|
||||
// this section is included to either let the service know up front
|
||||
// if the key will work, or let the service provider know which key
|
||||
// to use to decrypt the message. Either way, verification is not
|
||||
// security-critical.
|
||||
//nolint:revive,staticcheck // Keep the later empty branch so that we know to address this at a later date.
|
||||
if el := encryptedKey.FindElement("./KeyInfo/X509Data/X509Certificate"); el != nil {
|
||||
certPEMbuf := el.Text()
|
||||
certPEMbuf = "-----BEGIN CERTIFICATE-----\n" + certPEMbuf + "\n-----END CERTIFICATE-----\n"
|
||||
certPEM, _ := pem.Decode([]byte(certPEMbuf))
|
||||
if certPEM == nil {
|
||||
return nil, fmt.Errorf("invalid certificate")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(certPEM.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected certificate to be an *rsa.PublicKey")
|
||||
}
|
||||
if rsaKey.N.Cmp(pubKey.N) != 0 || rsaKey.E != pubKey.E {
|
||||
return nil, fmt.Errorf("certificate does not match provided key")
|
||||
}
|
||||
} else if el = encryptedKey.FindElement("./KeyInfo/X509Data/X509IssuerSerial"); el != nil {
|
||||
// TODO: determine how to validate the issuer serial information
|
||||
}
|
||||
return rsaKey, nil
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/sha1" //nolint:gosec // required for protocol support
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
|
||||
//nolint:staticcheck // We should support this for legacy reasons.
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
|
||||
type digestMethod struct {
|
||||
algorithm string
|
||||
hash func() hash.Hash
|
||||
}
|
||||
|
||||
func (dm digestMethod) Algorithm() string {
|
||||
return dm.algorithm
|
||||
}
|
||||
|
||||
func (dm digestMethod) Hash() hash.Hash {
|
||||
return dm.hash()
|
||||
}
|
||||
|
||||
var (
|
||||
// SHA1 implements the SHA-1 digest method (which is considered insecure)
|
||||
SHA1 = digestMethod{
|
||||
algorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
|
||||
hash: sha1.New,
|
||||
}
|
||||
|
||||
// SHA256 implements the SHA-256 digest method
|
||||
SHA256 = digestMethod{
|
||||
algorithm: "http://www.w3.org/2000/09/xmldsig#sha256",
|
||||
hash: sha256.New,
|
||||
}
|
||||
|
||||
// SHA512 implements the SHA-512 digest method
|
||||
SHA512 = digestMethod{
|
||||
algorithm: "http://www.w3.org/2000/09/xmldsig#sha512",
|
||||
hash: sha512.New,
|
||||
}
|
||||
|
||||
// RIPEMD160 implements the RIPEMD160 digest method
|
||||
RIPEMD160 = digestMethod{
|
||||
algorithm: "http://www.w3.org/2000/09/xmldsig#ripemd160",
|
||||
hash: ripemd160.New,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDigestMethod(SHA1)
|
||||
RegisterDigestMethod(SHA256)
|
||||
RegisterDigestMethod(SHA512)
|
||||
RegisterDigestMethod(RIPEMD160)
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
var testKey = func() *rsa.PrivateKey {
|
||||
const keyStr = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQDkXTUsWzRVpUHjbDpWCfYDfXmQ/q4LkaioZoTpu4ut1Q3eQC5t
|
||||
gD14agJhgT8yzeY5S/YNlwCyuVkjuFyoyTHFX2IOPpz7jnh4KnQ+B1IH9fY/+kmk
|
||||
zHJgxSUDJsdUMPgGpKt5hnEn7ziXAWXLc2udFbnHwhi9TXXwRHGi9wZ4YwIDAQAB
|
||||
AoGBALNTnlXeqRI4W61DZ+v4ln/XIIeD9xiOoWrcVrNU2zL+g41ryQmkEqFkXcpD
|
||||
vGUg2xFTXTz+v0WZ1y39sIW6uKFRYUfaNsF6iVfGAyx1VWK/jgtPnCWDQy26Eby0
|
||||
BqpbZRy1a6MLYVEG/5bvZE01CDV4XttpTrNX91WWcYGduJxBAkEA6ED1ZOqIzBpu
|
||||
c2KAo+bWmroCH8+cSDk0gVq6bnRB+EEhRCmo/VgvndWLxfexdGmDIOAIisB06N5a
|
||||
GzBSCaEY/QJBAPu2cNvuuBNLwrlxPCwOEpIHYT4gJq8UMtg6O6N+u++nYCGhK6uo
|
||||
VCmrKY+UewyNIcsLZF0jsNI2qJjiU1vQxN8CQQDfQJnigMQwlfO3/Ga1po6Buu2R
|
||||
0IpkroB3G1R8GkrTrR+iGv2zUdKrwHsUOC2fPlFrB4+OeMOomRw6aG9jjDStAkB1
|
||||
ztiZhuvuVAoKIv5HnDqC0CNqIUAZtzlozDB3f+xT6SFr+/Plfn4Nlod4JMVGhZNo
|
||||
ZaeOlBLBAEX+cAcVtOs/AkBicZOAPv84ABmFfyhXhYaAuacaJLq//jg+t+URUOg+
|
||||
XZS9naRmawEQxOkZQVoMeKgvu05+V4MniFqdQBINIkr5
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
b, _ := pem.Decode([]byte(keyStr))
|
||||
k, err := x509.ParsePKCS1PrivateKey(b.Bytes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return k
|
||||
}()
|
||||
|
||||
// Fuzz is the go-fuzz fuzzing function
|
||||
func Fuzz(data []byte) int {
|
||||
doc := etree.NewDocument()
|
||||
if err := doc.ReadFromBytes(data); err != nil {
|
||||
return 0
|
||||
}
|
||||
if doc.Root() == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if _, err := Decrypt(testKey, doc.Root()); err != nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// GCM implements Decrypter and Encrypter for block ciphers in struct mode
|
||||
type GCM struct {
|
||||
keySize int
|
||||
algorithm string
|
||||
cipher func([]byte) (cipher.Block, error)
|
||||
}
|
||||
|
||||
// KeySize returns the length of the key required.
|
||||
func (e GCM) KeySize() int {
|
||||
return e.keySize
|
||||
}
|
||||
|
||||
// Algorithm returns the name of the algorithm, as will be found
|
||||
// in an xenc:EncryptionMethod element.
|
||||
func (e GCM) Algorithm() string {
|
||||
return e.algorithm
|
||||
}
|
||||
|
||||
// Encrypt encrypts plaintext with key and nonce
|
||||
func (e GCM) Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) {
|
||||
keyBuf, ok := key.([]byte)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("[]byte")
|
||||
}
|
||||
if len(keyBuf) != e.keySize {
|
||||
return nil, ErrIncorrectKeyLength(e.keySize)
|
||||
}
|
||||
|
||||
block, err := e.cipher(keyBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encryptedDataEl := etree.NewElement("xenc:EncryptedData")
|
||||
encryptedDataEl.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
{
|
||||
randBuf := make([]byte, 16)
|
||||
if _, err := RandReader.Read(randBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedDataEl.CreateAttr("Id", fmt.Sprintf("_%x", randBuf))
|
||||
}
|
||||
|
||||
em := encryptedDataEl.CreateElement("xenc:EncryptionMethod")
|
||||
em.CreateAttr("Algorithm", e.algorithm)
|
||||
em.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
|
||||
plaintext = appendPadding(plaintext, block.BlockSize())
|
||||
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nonce == nil {
|
||||
// generate random nonce when it's nil
|
||||
nonce := make([]byte, aesgcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
text := aesgcm.Seal(nil, nonce, ciphertext, nil)
|
||||
|
||||
cd := encryptedDataEl.CreateElement("xenc:CipherData")
|
||||
cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(text))
|
||||
return encryptedDataEl, nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts an encrypted element with key. If the ciphertext contains an
|
||||
// EncryptedKey element, then the type of `key` is determined by the registered
|
||||
// Decryptor for the EncryptedKey element. Otherwise, `key` must be a []byte of
|
||||
// length KeySize().
|
||||
func (e GCM) Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) {
|
||||
if encryptedKeyEl := ciphertextEl.FindElement("./KeyInfo/EncryptedKey"); encryptedKeyEl != nil {
|
||||
var err error
|
||||
key, err = Decrypt(key, encryptedKeyEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
keyBuf, ok := key.([]byte)
|
||||
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("[]byte")
|
||||
}
|
||||
if len(keyBuf) != e.KeySize() {
|
||||
return nil, ErrIncorrectKeyLength(e.KeySize())
|
||||
}
|
||||
|
||||
block, err := e.cipher(keyBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesgcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext, err := getCiphertext(ciphertextEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := ciphertext[:aesgcm.NonceSize()]
|
||||
text := ciphertext[aesgcm.NonceSize():]
|
||||
|
||||
plainText, err := aesgcm.Open(nil, nonce, text, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return plainText, nil
|
||||
}
|
||||
|
||||
var (
|
||||
// AES128GCM implements AES128-GCM mode for encryption and decryption
|
||||
AES128GCM BlockCipher = GCM{
|
||||
keySize: 16,
|
||||
algorithm: "http://www.w3.org/2009/xmlenc11#aes128-gcm",
|
||||
cipher: aes.NewCipher,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDecrypter(AES128GCM)
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// RSA implements Encrypter and Decrypter using RSA public key encryption.
|
||||
//
|
||||
// Use function like OAEP(), or PKCS1v15() to get an instance of this type ready
|
||||
// to use.
|
||||
type RSA struct {
|
||||
BlockCipher BlockCipher
|
||||
DigestMethod DigestMethod // only for OAEP
|
||||
|
||||
algorithm string
|
||||
keyEncrypter func(e RSA, pubKey *rsa.PublicKey, plaintext []byte) ([]byte, error)
|
||||
keyDecrypter func(e RSA, privKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// Algorithm returns the name of the algorithm
|
||||
func (e RSA) Algorithm() string {
|
||||
return e.algorithm
|
||||
}
|
||||
|
||||
// Encrypt implements encrypter. certificate must be a []byte containing the ASN.1 bytes
|
||||
// of certificate containing an RSA public key.
|
||||
func (e RSA) Encrypt(certificate interface{}, plaintext []byte, nonce []byte) (*etree.Element, error) {
|
||||
cert, ok := certificate.(*x509.Certificate)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("*x.509 certificate")
|
||||
}
|
||||
|
||||
pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, ErrIncorrectKeyType("x.509 certificate with an RSA public key")
|
||||
}
|
||||
|
||||
// generate a key
|
||||
key := make([]byte, e.BlockCipher.KeySize())
|
||||
if _, err := RandReader.Read(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyInfoEl := etree.NewElement("ds:KeyInfo")
|
||||
keyInfoEl.CreateAttr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
|
||||
|
||||
encryptedKey := keyInfoEl.CreateElement("xenc:EncryptedKey")
|
||||
{
|
||||
randBuf := make([]byte, 16)
|
||||
if _, err := RandReader.Read(randBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedKey.CreateAttr("Id", fmt.Sprintf("_%x", randBuf))
|
||||
}
|
||||
encryptedKey.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
|
||||
encryptionMethodEl := encryptedKey.CreateElement("xenc:EncryptionMethod")
|
||||
encryptionMethodEl.CreateAttr("Algorithm", e.algorithm)
|
||||
encryptionMethodEl.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
if e.DigestMethod != nil {
|
||||
dm := encryptionMethodEl.CreateElement("ds:DigestMethod")
|
||||
dm.CreateAttr("Algorithm", e.DigestMethod.Algorithm())
|
||||
dm.CreateAttr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
|
||||
}
|
||||
{
|
||||
innerKeyInfoEl := encryptedKey.CreateElement("ds:KeyInfo")
|
||||
x509data := innerKeyInfoEl.CreateElement("ds:X509Data")
|
||||
x509data.CreateElement("ds:X509Certificate").SetText(
|
||||
base64.StdEncoding.EncodeToString(cert.Raw),
|
||||
)
|
||||
}
|
||||
|
||||
buf, err := e.keyEncrypter(e, pubKey, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cd := encryptedKey.CreateElement("xenc:CipherData")
|
||||
cd.CreateAttr("xmlns:xenc", "http://www.w3.org/2001/04/xmlenc#")
|
||||
cd.CreateElement("xenc:CipherValue").SetText(base64.StdEncoding.EncodeToString(buf))
|
||||
encryptedDataEl, err := e.BlockCipher.Encrypt(key, plaintext, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedDataEl.InsertChildAt(encryptedDataEl.FindElement("./CipherData").Index(), keyInfoEl)
|
||||
|
||||
return encryptedDataEl, nil
|
||||
}
|
||||
|
||||
// Decrypt implements Decryptor. `key` must be an *rsa.PrivateKey.
|
||||
func (e RSA) Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error) {
|
||||
rsaKey, err := validateRSAKeyIfPresent(key, ciphertextEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext, err := getCiphertext(ciphertextEl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
{
|
||||
digestMethodEl := ciphertextEl.FindElement("./EncryptionMethod/DigestMethod")
|
||||
if digestMethodEl == nil {
|
||||
e.DigestMethod = SHA1
|
||||
} else {
|
||||
hashAlgorithmStr := digestMethodEl.SelectAttrValue("Algorithm", "")
|
||||
digestMethod, ok := digestMethods[hashAlgorithmStr]
|
||||
if !ok {
|
||||
return nil, ErrAlgorithmNotImplemented(hashAlgorithmStr)
|
||||
}
|
||||
e.DigestMethod = digestMethod
|
||||
}
|
||||
}
|
||||
|
||||
return e.keyDecrypter(e, rsaKey, ciphertext)
|
||||
}
|
||||
|
||||
// OAEP returns a version of RSA that implements RSA in OAEP-MGF1P mode. By default
|
||||
// the block cipher used is AES-256 CBC and the digest method is SHA-256. You can
|
||||
// specify other ciphers and digest methods by assigning to BlockCipher or
|
||||
// DigestMethod.
|
||||
func OAEP() RSA {
|
||||
return RSA{
|
||||
BlockCipher: AES256CBC,
|
||||
DigestMethod: SHA256,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
|
||||
keyEncrypter: func(e RSA, pubKey *rsa.PublicKey, plaintext []byte) ([]byte, error) {
|
||||
return rsa.EncryptOAEP(e.DigestMethod.Hash(), RandReader, pubKey, plaintext, nil)
|
||||
},
|
||||
keyDecrypter: func(e RSA, privKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
|
||||
return rsa.DecryptOAEP(e.DigestMethod.Hash(), RandReader, privKey, ciphertext, nil)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// PKCS1v15 returns a version of RSA that implements RSA in PKCS1v15 mode. By default
|
||||
// the block cipher used is AES-256 CBC. The DigestMethod field is ignored because PKCS1v15
|
||||
// does not use a digest function.
|
||||
func PKCS1v15() RSA {
|
||||
return RSA{
|
||||
BlockCipher: AES256CBC,
|
||||
DigestMethod: nil,
|
||||
algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
|
||||
keyEncrypter: func(e RSA, pubKey *rsa.PublicKey, plaintext []byte) ([]byte, error) {
|
||||
return rsa.EncryptPKCS1v15(RandReader, pubKey, plaintext)
|
||||
},
|
||||
keyDecrypter: func(e RSA, privKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
|
||||
return rsa.DecryptPKCS1v15(RandReader, privKey, ciphertext)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterDecrypter(OAEP())
|
||||
RegisterDecrypter(PKCS1v15())
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// Package xmlenc is a partial implementation of the xmlenc standard
|
||||
// as described in https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html.
|
||||
// The purpose of this implementation is to support encrypted SAML assertions.
|
||||
package xmlenc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"hash"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
)
|
||||
|
||||
// RandReader is a thunk that allows test to replace the source of randomness used by
|
||||
// this package. By default it is Reader from crypto/rand.
|
||||
var RandReader = rand.Reader
|
||||
|
||||
// Encrypter is an interface that encrypts things. Given a plaintext it returns an
|
||||
// XML EncryptedData or EncryptedKey element. The required type of `key` varies
|
||||
// depending on the implementation.
|
||||
type Encrypter interface {
|
||||
Encrypt(key interface{}, plaintext []byte, nonce []byte) (*etree.Element, error)
|
||||
}
|
||||
|
||||
// Decrypter is an interface that decrypts things. The Decrypt() method returns the
|
||||
// plaintext version of the EncryptedData or EncryptedKey element passed.
|
||||
//
|
||||
// You probably don't have to use this interface directly, instead you may call
|
||||
// Decrypt() and it will examine the element to determine which Decrypter to use.
|
||||
type Decrypter interface {
|
||||
Algorithm() string
|
||||
Decrypt(key interface{}, ciphertextEl *etree.Element) ([]byte, error)
|
||||
}
|
||||
|
||||
// DigestMethod represents a digest method such as SHA1, etc.
|
||||
type DigestMethod interface {
|
||||
Algorithm() string
|
||||
Hash() hash.Hash
|
||||
}
|
||||
|
||||
var (
|
||||
decrypters = map[string]Decrypter{}
|
||||
digestMethods = map[string]DigestMethod{}
|
||||
)
|
||||
|
||||
// RegisterDecrypter registers the specified decrypter to that it can be
|
||||
// used with Decrypt().
|
||||
func RegisterDecrypter(d Decrypter) {
|
||||
decrypters[d.Algorithm()] = d
|
||||
}
|
||||
|
||||
// RegisterDigestMethod registers the specified digest method to that it can be
|
||||
// used with Decrypt().
|
||||
func RegisterDigestMethod(dm DigestMethod) {
|
||||
digestMethods[dm.Algorithm()] = dm
|
||||
}
|
||||
|
||||
// BlockCipher implements a cipher with a fixed size key like AES or 3DES.
|
||||
type BlockCipher interface {
|
||||
Encrypter
|
||||
Decrypter
|
||||
KeySize() int
|
||||
}
|
||||
Reference in New Issue
Block a user