添加 github.com/pion/dtls 代码

This commit is contained in:
bjdgyc
2021-05-21 19:03:00 +08:00
parent 54a0cb7928
commit 28b5119f50
380 changed files with 16870 additions and 0 deletions

View File

@@ -0,0 +1,251 @@
// Package ccm implements a CCM, Counter with CBC-MAC
// as per RFC 3610.
//
// See https://tools.ietf.org/html/rfc3610
//
// This code was lifted from https://github.com/bocajim/dtls/blob/a3300364a283fcb490d28a93d7fcfa7ba437fbbe/ccm/ccm.go
// and as such was not written by the Pions authors. Like Pions this
// code is licensed under MIT.
//
// A request for including CCM into the Go standard library
// can be found as issue #27484 on the https://github.com/golang/go/
// repository.
package ccm
import (
"crypto/cipher"
"crypto/subtle"
"encoding/binary"
"errors"
"math"
)
// ccm represents a Counter with CBC-MAC with a specific key.
type ccm struct {
b cipher.Block
M uint8
L uint8
}
const ccmBlockSize = 16
// CCM is a block cipher in Counter with CBC-MAC mode.
// Providing authenticated encryption with associated data via the cipher.AEAD interface.
type CCM interface {
cipher.AEAD
// MaxLength returns the maxium length of plaintext in calls to Seal.
// The maximum length of ciphertext in calls to Open is MaxLength()+Overhead().
// The maximum length is related to CCM's `L` parameter (15-noncesize) and
// is 1<<(8*L) - 1 (but also limited by the maxium size of an int).
MaxLength() int
}
var (
errInvalidBlockSize = errors.New("ccm: NewCCM requires 128-bit block cipher")
errInvalidTagSize = errors.New("ccm: tagsize must be 4, 6, 8, 10, 12, 14, or 16")
errInvalidNonceSize = errors.New("ccm: invalid nonce size")
)
// NewCCM returns the given 128-bit block cipher wrapped in CCM.
// The tagsize must be an even integer between 4 and 16 inclusive
// and is used as CCM's `M` parameter.
// The noncesize must be an integer between 7 and 13 inclusive,
// 15-noncesize is used as CCM's `L` parameter.
func NewCCM(b cipher.Block, tagsize, noncesize int) (CCM, error) {
if b.BlockSize() != ccmBlockSize {
return nil, errInvalidBlockSize
}
if tagsize < 4 || tagsize > 16 || tagsize&1 != 0 {
return nil, errInvalidTagSize
}
lensize := 15 - noncesize
if lensize < 2 || lensize > 8 {
return nil, errInvalidNonceSize
}
c := &ccm{b: b, M: uint8(tagsize), L: uint8(lensize)}
return c, nil
}
func (c *ccm) NonceSize() int { return 15 - int(c.L) }
func (c *ccm) Overhead() int { return int(c.M) }
func (c *ccm) MaxLength() int { return maxlen(c.L, c.Overhead()) }
func maxlen(l uint8, tagsize int) int {
max := (uint64(1) << (8 * l)) - 1
if m64 := uint64(math.MaxInt64) - uint64(tagsize); l > 8 || max > m64 {
max = m64 // The maximum lentgh on a 64bit arch
}
if max != uint64(int(max)) {
return math.MaxInt32 - tagsize // We have only 32bit int's
}
return int(max)
}
// MaxNonceLength returns the maximum nonce length for a given plaintext length.
// A return value <= 0 indicates that plaintext length is too large for
// any nonce length.
func MaxNonceLength(pdatalen int) int {
const tagsize = 16
for L := 2; L <= 8; L++ {
if maxlen(uint8(L), tagsize) >= pdatalen {
return 15 - L
}
}
return 0
}
func (c *ccm) cbcRound(mac, data []byte) {
for i := 0; i < ccmBlockSize; i++ {
mac[i] ^= data[i]
}
c.b.Encrypt(mac, mac)
}
func (c *ccm) cbcData(mac, data []byte) {
for len(data) >= ccmBlockSize {
c.cbcRound(mac, data[:ccmBlockSize])
data = data[ccmBlockSize:]
}
if len(data) > 0 {
var block [ccmBlockSize]byte
copy(block[:], data)
c.cbcRound(mac, block[:])
}
}
var errPlaintextTooLong = errors.New("ccm: plaintext too large")
func (c *ccm) tag(nonce, plaintext, adata []byte) ([]byte, error) {
var mac [ccmBlockSize]byte
if len(adata) > 0 {
mac[0] |= 1 << 6
}
mac[0] |= (c.M - 2) << 2
mac[0] |= c.L - 1
if len(nonce) != c.NonceSize() {
return nil, errInvalidNonceSize
}
if len(plaintext) > c.MaxLength() {
return nil, errPlaintextTooLong
}
binary.BigEndian.PutUint64(mac[ccmBlockSize-8:], uint64(len(plaintext)))
copy(mac[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(mac[:], mac[:])
var block [ccmBlockSize]byte
if n := uint64(len(adata)); n > 0 {
// First adata block includes adata length
i := 2
if n <= 0xfeff {
binary.BigEndian.PutUint16(block[:i], uint16(n))
} else {
block[0] = 0xfe
block[1] = 0xff
if n < uint64(1<<32) {
i = 2 + 4
binary.BigEndian.PutUint32(block[2:i], uint32(n))
} else {
i = 2 + 8
binary.BigEndian.PutUint64(block[2:i], n)
}
}
i = copy(block[i:], adata)
c.cbcRound(mac[:], block[:])
c.cbcData(mac[:], adata[i:])
}
if len(plaintext) > 0 {
c.cbcData(mac[:], plaintext)
}
return mac[:c.M], nil
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
// From crypto/cipher/gcm.go
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}
// Seal encrypts and authenticates plaintext, authenticates the
// additional data and appends the result to dst, returning the updated
// slice. The nonce must be NonceSize() bytes long and unique for all
// time, for a given key.
// The plaintext must be no longer than MaxLength() bytes long.
//
// The plaintext and dst may alias exactly or not at all.
func (c *ccm) Seal(dst, nonce, plaintext, adata []byte) []byte {
tag, err := c.tag(nonce, plaintext, adata)
if err != nil {
// The cipher.AEAD interface doesn't allow for an error return.
panic(err) // nolint
}
var iv, s0 [ccmBlockSize]byte
iv[0] = c.L - 1
copy(iv[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(s0[:], iv[:])
for i := 0; i < int(c.M); i++ {
tag[i] ^= s0[i]
}
iv[len(iv)-1] |= 1
stream := cipher.NewCTR(c.b, iv[:])
ret, out := sliceForAppend(dst, len(plaintext)+int(c.M))
stream.XORKeyStream(out, plaintext)
copy(out[len(plaintext):], tag)
return ret
}
var (
errOpen = errors.New("ccm: message authentication failed")
errCiphertextTooShort = errors.New("ccm: ciphertext too short")
errCiphertextTooLong = errors.New("ccm: ciphertext too long")
)
func (c *ccm) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
if len(ciphertext) < int(c.M) {
return nil, errCiphertextTooShort
}
if len(ciphertext) > c.MaxLength()+c.Overhead() {
return nil, errCiphertextTooLong
}
tag := make([]byte, int(c.M))
copy(tag, ciphertext[len(ciphertext)-int(c.M):])
ciphertextWithoutTag := ciphertext[:len(ciphertext)-int(c.M)]
var iv, s0 [ccmBlockSize]byte
iv[0] = c.L - 1
copy(iv[1:ccmBlockSize-c.L], nonce)
c.b.Encrypt(s0[:], iv[:])
for i := 0; i < int(c.M); i++ {
tag[i] ^= s0[i]
}
iv[len(iv)-1] |= 1
stream := cipher.NewCTR(c.b, iv[:])
// Cannot decrypt directly to dst since we're not supposed to
// reveal the plaintext to the caller if authentication fails.
plaintext := make([]byte, len(ciphertextWithoutTag))
stream.XORKeyStream(plaintext, ciphertextWithoutTag)
expectedTag, err := c.tag(nonce, plaintext, adata)
if err != nil {
return nil, err
}
if subtle.ConstantTimeCompare(tag, expectedTag) != 1 {
return nil, errOpen
}
return append(dst, plaintext...), nil
}

View File

@@ -0,0 +1,419 @@
package ccm
// Refer to RFC 3610 section 8 for the vectors.
import (
"bytes"
"crypto/aes"
"encoding/hex"
"errors"
"fmt"
"testing"
)
func mustHexDecode(s string) []byte {
r, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return r
}
var (
aesKey1to12 = mustHexDecode("c0c1c2c3c4c5c6c7c8c9cacbcccdcecf") //nolint:gochecknoglobals
aesKey13to24 = mustHexDecode("d7828d13b2b0bdc325a76236df93cc6b") //nolint:gochecknoglobals
)
// AESKey: AES Key
// CipherText: Authenticated and encrypted output
// ClearHeaderOctets: Input with X cleartext header octets
// Data: Input with X cleartext header octets
// M: length(CBC-MAC)
// Nonce: Nonce
type vector struct {
AESKey []byte
CipherText []byte
ClearHeaderOctets int
Data []byte
M int
Nonce []byte
}
func TestRFC3610Vectors(t *testing.T) {
cases := []vector{
// Vectors 1-12
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("0001020304050607588c979a61c663d2f066d0c2c0f989806d5f6b61dac38417e8d12cfdf926e0"),
ClearHeaderOctets: 8,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 8,
Nonce: mustHexDecode("00000003020100a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060772c91a36e135f8cf291ca894085c87e3cc15c439c9e43a3ba091d56e10400916"),
ClearHeaderOctets: 8,
Data: mustHexDecode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"),
M: 8,
Nonce: mustHexDecode("00000004030201a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060751b1e5f44a197d1da46b0f8e2d282ae871e838bb64da8596574adaa76fbd9fb0c5"),
ClearHeaderOctets: 8,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
M: 8,
Nonce: mustHexDecode("00000005040302a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060708090a0ba28c6865939a9a79faaa5c4c2a9d4a91cdac8c96c861b9c9e61ef1"),
ClearHeaderOctets: 12,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 8,
Nonce: mustHexDecode("00000006050403a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060708090a0bdcf1fb7b5d9e23fb9d4e131253658ad86ebdca3e51e83f077d9c2d93"),
ClearHeaderOctets: 12,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
M: 8,
Nonce: mustHexDecode("00000007060504a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060708090a0b6fc1b011f006568b5171a42d953d469b2570a4bd87405a0443ac91cb94"),
ClearHeaderOctets: 12,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
M: 8,
Nonce: mustHexDecode("00000008070605a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("00010203040506070135d1b2c95f41d5d1d4fec185d166b8094e999dfed96c048c56602c97acbb7490"),
ClearHeaderOctets: 8,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 10,
Nonce: mustHexDecode("00000009080706a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("00010203040506077b75399ac0831dd2f0bbd75879a2fd8f6cae6b6cd9b7db24c17b4433f434963f34b4"),
ClearHeaderOctets: 8,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
M: 10,
Nonce: mustHexDecode("0000000a090807a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060782531a60cc24945a4b8279181ab5c84df21ce7f9b73f42e197ea9c07e56b5eb17e5f4e"),
ClearHeaderOctets: 8,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
M: 10,
Nonce: mustHexDecode("0000000b0a0908a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060708090a0b07342594157785152b074098330abb141b947b566aa9406b4d999988dd"),
ClearHeaderOctets: 12,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 10,
Nonce: mustHexDecode("0000000c0b0a09a0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060708090a0b676bb20380b0e301e8ab79590a396da78b834934f53aa2e9107a8b6c022c"),
ClearHeaderOctets: 12,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
M: 10,
Nonce: mustHexDecode("0000000d0c0b0aa0a1a2a3a4a5"),
},
{
AESKey: aesKey1to12,
CipherText: mustHexDecode("000102030405060708090a0bc0ffa0d6f05bdb67f24d43a4338d2aa4bed7b20e43cd1aa31662e7ad65d6db"),
ClearHeaderOctets: 12,
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"),
M: 10,
Nonce: mustHexDecode("0000000e0d0c0ba0a1a2a3a4a5"),
},
// Vectors 13-24
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("0be1a88bace018b14cb97f86a2a4689a877947ab8091ef5386a6ffbdd080f8e78cf7cb0cddd7b3"),
ClearHeaderOctets: 8,
Data: mustHexDecode("0be1a88bace018b108e8cf97d820ea258460e96ad9cf5289054d895ceac47c"),
M: 8,
Nonce: mustHexDecode("00412b4ea9cdbe3c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("63018f76dc8a1bcb4ccb1e7ca981befaa0726c55d378061298c85c92814abc33c52ee81d7d77c08a"),
ClearHeaderOctets: 8,
Data: mustHexDecode("63018f76dc8a1bcb9020ea6f91bdd85afa0039ba4baff9bfb79c7028949cd0ec"),
M: 8,
Nonce: mustHexDecode("0033568ef7b2633c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("aa6cfa36cae86b40b1d23a2220ddc0ac900d9aa03c61fcf4a559a4417767089708a776796edb723506"),
ClearHeaderOctets: 8,
Data: mustHexDecode("aa6cfa36cae86b40b916e0eacc1c00d7dcec68ec0b3bbb1a02de8a2d1aa346132e"),
M: 8,
Nonce: mustHexDecode("00103fe41336713c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("d0d0735c531e1becf049c24414d253c3967b70609b7cbb7c499160283245269a6f49975bcadeaf"),
ClearHeaderOctets: 12,
Data: mustHexDecode("d0d0735c531e1becf049c24412daac5630efa5396f770ce1a66b21f7b2101c"),
M: 8,
Nonce: mustHexDecode("00764c63b8058e3c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("77b60f011c03e1525899bcae5545ff1a085ee2efbf52b2e04bee1e2336c73e3f762c0c7744fe7e3c"),
ClearHeaderOctets: 12,
Data: mustHexDecode("77b60f011c03e1525899bcaee88b6a46c78d63e52eb8c546efb5de6f75e9cc0d"),
M: 8,
Nonce: mustHexDecode("00f8b678094e3b3c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("cd9044d2b71fdb8120ea60c0009769ecabdf48625594c59251e6035722675e04c847099e5ae0704551"),
ClearHeaderOctets: 12,
Data: mustHexDecode("cd9044d2b71fdb8120ea60c06435acbafb11a82e2f071d7ca4a5ebd93a803ba87f"),
M: 8,
Nonce: mustHexDecode("00d560912d3f703c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("d85bc7e69f944fb8bc218daa947427b6db386a99ac1aef23ade0b52939cb6a637cf9bec2408897c6ba"),
ClearHeaderOctets: 8,
Data: mustHexDecode("d85bc7e69f944fb88a19b950bcf71a018e5e6701c91787659809d67dbedd18"),
M: 10,
Nonce: mustHexDecode("0042fff8f1951c3c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("74a0ebc9069f5b375810e6fd25874022e80361a478e3e9cf484ab04f447efff6f0a477cc2fc9bf548944"),
ClearHeaderOctets: 8,
Data: mustHexDecode("74a0ebc9069f5b371761433c37c5a35fc1f39f406302eb907c6163be38c98437"),
M: 10,
Nonce: mustHexDecode("00920f40e56cdc3c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("44a3aa3aae6475caf2beed7bc5098e83feb5b31608f8e29c38819a89c8e776f1544d4151a4ed3a8b87b9ce"),
ClearHeaderOctets: 8,
Data: mustHexDecode("44a3aa3aae6475caa434a8e58500c6e41530538862d686ea9e81301b5ae4226bfa"),
M: 10,
Nonce: mustHexDecode("0027ca0c7120bc3c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("ec46bb63b02520c33c49fd7031d750a09da3ed7fddd49a2032aabf17ec8ebf7d22c8088c666be5c197"),
ClearHeaderOctets: 12,
Data: mustHexDecode("ec46bb63b02520c33c49fd70b96b49e21d621741632875db7f6c9243d2d7c2"),
M: 10,
Nonce: mustHexDecode("005b8ccbcd9af83c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("47a65ac78b3d594227e85e71e882f1dbd38ce3eda7c23f04dd65071eb41342acdf7e00dccec7ae52987d"),
ClearHeaderOctets: 12,
Data: mustHexDecode("47a65ac78b3d594227e85e71e2fcfbb880442c731bf95167c8ffd7895e337076"),
M: 10,
Nonce: mustHexDecode("003ebe94044b9a3c9696766cfa"),
},
{
AESKey: aesKey13to24,
CipherText: mustHexDecode("6e37a6ef546d955d34ab6059f32905b88a641b04b9c9ffb58cc390900f3da12ab16dce9e82efa16da62059"),
ClearHeaderOctets: 12,
Data: mustHexDecode("6e37a6ef546d955d34ab6059abf21c0b02feb88f856df4a37381bce3cc128517d4"),
M: 10,
Nonce: mustHexDecode("008d493b30ae8b3c9696766cfa"),
},
}
if len(cases) != 24 {
t.Fatalf("Expected %d test cases, got: %d", 24, len(cases))
t.FailNow()
}
for idx, c := range cases {
c := c
t.Run(fmt.Sprintf("packet vector #%d", idx+1), func(t *testing.T) {
t.Parallel()
blk, err := aes.NewCipher(c.AESKey)
if err != nil {
t.Fatalf("could not initialize AES block cipher from key: %v", err)
}
lccm, err := NewCCM(blk, c.M, len(c.Nonce))
if err != nil {
t.Fatalf("could not create CCM: %v", err)
}
t.Run("seal", func(t *testing.T) {
var dst []byte
dst = lccm.Seal(dst, c.Nonce, c.Data[c.ClearHeaderOctets:], c.Data[:c.ClearHeaderOctets])
if !bytes.Equal(c.CipherText[c.ClearHeaderOctets:], dst) {
t.Fatalf("ciphertext does not match, wanted %v, got %v",
c.CipherText[c.ClearHeaderOctets:], dst)
}
})
t.Run("open", func(t *testing.T) {
var dst []byte
dst, err = lccm.Open(dst, c.Nonce, c.CipherText[c.ClearHeaderOctets:], c.CipherText[:c.ClearHeaderOctets])
if err != nil {
t.Fatalf("failed to unseal: %v", err)
}
if !bytes.Equal(c.Data[c.ClearHeaderOctets:], dst) {
t.Fatalf("plaintext does not match, wanted %v, got %v",
c.Data[c.ClearHeaderOctets:], dst)
}
})
})
}
}
func TestNewCCMError(t *testing.T) {
cases := map[string]struct {
vector
err error
}{
"ShortNonceLength": {
vector{
AESKey: aesKey1to12,
M: 8,
Nonce: mustHexDecode("a0a1a2a3a4a5"),
}, errInvalidNonceSize,
},
"LongNonceLength": {
vector{
AESKey: aesKey1to12,
M: 8,
Nonce: mustHexDecode("0001020304050607080910111213"),
}, errInvalidNonceSize,
},
"ShortTag": {
vector{
AESKey: aesKey1to12,
M: 3,
Nonce: mustHexDecode("00010203040506070809101112"),
}, errInvalidTagSize,
},
"LongTag": {
vector{
AESKey: aesKey1to12,
M: 17,
Nonce: mustHexDecode("00010203040506070809101112"),
}, errInvalidTagSize,
},
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
blk, err := aes.NewCipher(c.AESKey)
if err != nil {
t.Fatalf("could not initialize AES block cipher from key: %v", err)
}
if _, err := NewCCM(blk, c.M, len(c.Nonce)); !errors.Is(err, c.err) {
t.Fatalf("expected error '%v', got '%v'", c.err, err)
}
})
}
}
func TestSealError(t *testing.T) {
cases := map[string]struct {
vector
err error
}{
"InvalidNonceLength": {
vector{
Data: mustHexDecode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e"),
M: 8,
Nonce: mustHexDecode("00000003020100a0a1a2a3a4"), // short
}, errInvalidNonceSize,
},
"PlaintextTooLong": {
vector{
Data: make([]byte, 100000),
M: 8,
Nonce: mustHexDecode("00000003020100a0a1a2a3a4a5"),
}, errPlaintextTooLong,
},
}
blk, err := aes.NewCipher(aesKey1to12)
if err != nil {
t.Fatalf("could not initialize AES block cipher from key: %v", err)
}
lccm, err := NewCCM(blk, 8, 13)
if err != nil {
t.Fatalf("could not create CCM: %v", err)
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
defer func() {
if err := recover(); !errors.Is(err.(error), c.err) {
t.Errorf("expected panic '%v', got '%v'", c.err, err)
}
}()
var dst []byte
_ = lccm.Seal(dst, c.Nonce, c.Data[c.ClearHeaderOctets:], c.Data[:c.ClearHeaderOctets])
})
}
}
func TestOpenError(t *testing.T) {
cases := map[string]struct {
vector
err error
}{
"CiphertextTooShort": {
vector{
CipherText: make([]byte, 10),
ClearHeaderOctets: 8,
Nonce: mustHexDecode("00000003020100a0a1a2a3a4a5"),
}, errCiphertextTooShort,
},
"CiphertextTooLong": {
vector{
CipherText: make([]byte, 100000),
ClearHeaderOctets: 8,
Nonce: mustHexDecode("00000003020100a0a1a2a3a4a5"),
}, errCiphertextTooLong,
},
}
blk, err := aes.NewCipher(aesKey1to12)
if err != nil {
t.Fatalf("could not initialize AES block cipher from key: %v", err)
}
lccm, err := NewCCM(blk, 8, 13)
if err != nil {
t.Fatalf("could not create CCM: %v", err)
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
var dst []byte
_, err = lccm.Open(dst, c.Nonce, c.CipherText[c.ClearHeaderOctets:], c.CipherText[:c.ClearHeaderOctets])
if !errors.Is(err, c.err) {
t.Errorf("expected error '%v', got '%v'", c.err, err)
}
})
}
}

View File

@@ -0,0 +1,164 @@
package ciphersuite
import ( //nolint:gci
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"encoding/binary"
"hash"
"github.com/pion/dtls/v2/internal/util"
"github.com/pion/dtls/v2/pkg/crypto/prf"
"github.com/pion/dtls/v2/pkg/protocol"
"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
)
// block ciphers using cipher block chaining.
type cbcMode interface {
cipher.BlockMode
SetIV([]byte)
}
// CBC Provides an API to Encrypt/Decrypt DTLS 1.2 Packets
type CBC struct {
writeCBC, readCBC cbcMode
writeMac, readMac []byte
h prf.HashFunc
}
// NewCBC creates a DTLS CBC Cipher
func NewCBC(localKey, localWriteIV, localMac, remoteKey, remoteWriteIV, remoteMac []byte, h prf.HashFunc) (*CBC, error) {
writeBlock, err := aes.NewCipher(localKey)
if err != nil {
return nil, err
}
readBlock, err := aes.NewCipher(remoteKey)
if err != nil {
return nil, err
}
return &CBC{
writeCBC: cipher.NewCBCEncrypter(writeBlock, localWriteIV).(cbcMode),
writeMac: localMac,
readCBC: cipher.NewCBCDecrypter(readBlock, remoteWriteIV).(cbcMode),
readMac: remoteMac,
h: h,
}, nil
}
// Encrypt encrypt a DTLS RecordLayer message
func (c *CBC) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
payload := raw[recordlayer.HeaderSize:]
raw = raw[:recordlayer.HeaderSize]
blockSize := c.writeCBC.BlockSize()
// Generate + Append MAC
h := pkt.Header
MAC, err := c.hmac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, payload, c.writeMac, c.h)
if err != nil {
return nil, err
}
payload = append(payload, MAC...)
// Generate + Append padding
padding := make([]byte, blockSize-len(payload)%blockSize)
paddingLen := len(padding)
for i := 0; i < paddingLen; i++ {
padding[i] = byte(paddingLen - 1)
}
payload = append(payload, padding...)
// Generate IV
iv := make([]byte, blockSize)
if _, err := rand.Read(iv); err != nil {
return nil, err
}
// Set IV + Encrypt + Prepend IV
c.writeCBC.SetIV(iv)
c.writeCBC.CryptBlocks(payload, payload)
payload = append(iv, payload...)
// Prepend unencrypte header with encrypted payload
raw = append(raw, payload...)
// Update recordLayer size to include IV+MAC+Padding
binary.BigEndian.PutUint16(raw[recordlayer.HeaderSize-2:], uint16(len(raw)-recordlayer.HeaderSize))
return raw, nil
}
// Decrypt decrypts a DTLS RecordLayer message
func (c *CBC) Decrypt(in []byte) ([]byte, error) {
body := in[recordlayer.HeaderSize:]
blockSize := c.readCBC.BlockSize()
mac := c.h()
var h recordlayer.Header
err := h.Unmarshal(in)
switch {
case err != nil:
return nil, err
case h.ContentType == protocol.ContentTypeChangeCipherSpec:
// Nothing to encrypt with ChangeCipherSpec
return in, nil
case len(body)%blockSize != 0 || len(body) < blockSize+util.Max(mac.Size()+1, blockSize):
return nil, errNotEnoughRoomForNonce
}
// Set + remove per record IV
c.readCBC.SetIV(body[:blockSize])
body = body[blockSize:]
// Decrypt
c.readCBC.CryptBlocks(body, body)
// Padding+MAC needs to be checked in constant time
// Otherwise we reveal information about the level of correctness
paddingLen, paddingGood := examinePadding(body)
if paddingGood != 255 {
return nil, errInvalidMAC
}
macSize := mac.Size()
if len(body) < macSize {
return nil, errInvalidMAC
}
dataEnd := len(body) - macSize - paddingLen
expectedMAC := body[dataEnd : dataEnd+macSize]
actualMAC, err := c.hmac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, body[:dataEnd], c.readMac, c.h)
// Compute Local MAC and compare
if err != nil || !hmac.Equal(actualMAC, expectedMAC) {
return nil, errInvalidMAC
}
return append(in[:recordlayer.HeaderSize], body[:dataEnd]...), nil
}
func (c *CBC) hmac(epoch uint16, sequenceNumber uint64, contentType protocol.ContentType, protocolVersion protocol.Version, payload []byte, key []byte, hf func() hash.Hash) ([]byte, error) {
h := hmac.New(hf, key)
msg := make([]byte, 13)
binary.BigEndian.PutUint16(msg, epoch)
util.PutBigEndianUint48(msg[2:], sequenceNumber)
msg[8] = byte(contentType)
msg[9] = protocolVersion.Major
msg[10] = protocolVersion.Minor
binary.BigEndian.PutUint16(msg[11:], uint16(len(payload)))
if _, err := h.Write(msg); err != nil {
return nil, err
} else if _, err := h.Write(payload); err != nil {
return nil, err
}
return h.Sum(nil), nil
}

View File

@@ -0,0 +1,104 @@
package ciphersuite
import (
"crypto/aes"
"crypto/rand"
"encoding/binary"
"fmt"
"github.com/pion/dtls/v2/pkg/crypto/ccm"
"github.com/pion/dtls/v2/pkg/protocol"
"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
)
// CCMTagLen is the length of Authentication Tag
type CCMTagLen int
// CCM Enums
const (
CCMTagLength8 CCMTagLen = 8
CCMTagLength CCMTagLen = 16
ccmNonceLength = 12
)
// CCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets
type CCM struct {
localCCM, remoteCCM ccm.CCM
localWriteIV, remoteWriteIV []byte
tagLen CCMTagLen
}
// NewCCM creates a DTLS GCM Cipher
func NewCCM(tagLen CCMTagLen, localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*CCM, error) {
localBlock, err := aes.NewCipher(localKey)
if err != nil {
return nil, err
}
localCCM, err := ccm.NewCCM(localBlock, int(tagLen), ccmNonceLength)
if err != nil {
return nil, err
}
remoteBlock, err := aes.NewCipher(remoteKey)
if err != nil {
return nil, err
}
remoteCCM, err := ccm.NewCCM(remoteBlock, int(tagLen), ccmNonceLength)
if err != nil {
return nil, err
}
return &CCM{
localCCM: localCCM,
localWriteIV: localWriteIV,
remoteCCM: remoteCCM,
remoteWriteIV: remoteWriteIV,
tagLen: tagLen,
}, nil
}
// Encrypt encrypt a DTLS RecordLayer message
func (c *CCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
payload := raw[recordlayer.HeaderSize:]
raw = raw[:recordlayer.HeaderSize]
nonce := append(append([]byte{}, c.localWriteIV[:4]...), make([]byte, 8)...)
if _, err := rand.Read(nonce[4:]); err != nil {
return nil, err
}
additionalData := generateAEADAdditionalData(&pkt.Header, len(payload))
encryptedPayload := c.localCCM.Seal(nil, nonce, payload, additionalData)
encryptedPayload = append(nonce[4:], encryptedPayload...)
raw = append(raw, encryptedPayload...)
// Update recordLayer size to include explicit nonce
binary.BigEndian.PutUint16(raw[recordlayer.HeaderSize-2:], uint16(len(raw)-recordlayer.HeaderSize))
return raw, nil
}
// Decrypt decrypts a DTLS RecordLayer message
func (c *CCM) Decrypt(in []byte) ([]byte, error) {
var h recordlayer.Header
err := h.Unmarshal(in)
switch {
case err != nil:
return nil, err
case h.ContentType == protocol.ContentTypeChangeCipherSpec:
// Nothing to encrypt with ChangeCipherSpec
return in, nil
case len(in) <= (8 + recordlayer.HeaderSize):
return nil, errNotEnoughRoomForNonce
}
nonce := append(append([]byte{}, c.remoteWriteIV[:4]...), in[recordlayer.HeaderSize:recordlayer.HeaderSize+8]...)
out := in[recordlayer.HeaderSize+8:]
additionalData := generateAEADAdditionalData(&h, len(out)-int(c.tagLen))
out, err = c.remoteCCM.Open(out[:0], nonce, out, additionalData)
if err != nil {
return nil, fmt.Errorf("%w: %v", errDecryptPacket, err)
}
return append(in[:recordlayer.HeaderSize], out...), nil
}

View File

@@ -0,0 +1,72 @@
// Package ciphersuite provides the crypto operations needed for a DTLS CipherSuite
package ciphersuite
import (
"encoding/binary"
"errors"
"github.com/pion/dtls/v2/pkg/protocol"
"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
)
var (
errNotEnoughRoomForNonce = &protocol.InternalError{Err: errors.New("buffer not long enough to contain nonce")} //nolint:goerr113
errDecryptPacket = &protocol.TemporaryError{Err: errors.New("failed to decrypt packet")} //nolint:goerr113
errInvalidMAC = &protocol.TemporaryError{Err: errors.New("invalid mac")} //nolint:goerr113
)
func generateAEADAdditionalData(h *recordlayer.Header, payloadLen int) []byte {
var additionalData [13]byte
// SequenceNumber MUST be set first
// we only want uint48, clobbering an extra 2 (using uint64, Golang doesn't have uint48)
binary.BigEndian.PutUint64(additionalData[:], h.SequenceNumber)
binary.BigEndian.PutUint16(additionalData[:], h.Epoch)
additionalData[8] = byte(h.ContentType)
additionalData[9] = h.Version.Major
additionalData[10] = h.Version.Minor
binary.BigEndian.PutUint16(additionalData[len(additionalData)-2:], uint16(payloadLen))
return additionalData[:]
}
// examinePadding returns, in constant time, the length of the padding to remove
// from the end of payload. It also returns a byte which is equal to 255 if the
// padding was valid and 0 otherwise. See RFC 2246, Section 6.2.3.2.
//
// https://github.com/golang/go/blob/039c2081d1178f90a8fa2f4e6958693129f8de33/src/crypto/tls/conn.go#L245
func examinePadding(payload []byte) (toRemove int, good byte) {
if len(payload) < 1 {
return 0, 0
}
paddingLen := payload[len(payload)-1]
t := uint(len(payload)-1) - uint(paddingLen)
// if len(payload) >= (paddingLen - 1) then the MSB of t is zero
good = byte(int32(^t) >> 31)
// The maximum possible padding length plus the actual length field
toCheck := 256
// The length of the padded data is public, so we can use an if here
if toCheck > len(payload) {
toCheck = len(payload)
}
for i := 0; i < toCheck; i++ {
t := uint(paddingLen) - uint(i)
// if i <= paddingLen then the MSB of t is zero
mask := byte(int32(^t) >> 31)
b := payload[len(payload)-1-i]
good &^= mask&paddingLen ^ mask&b
}
// We AND together the bits of good and replicate the result across
// all the bits.
good &= good << 4
good &= good << 2
good &= good << 1
good = uint8(int8(good) >> 7)
toRemove = int(paddingLen) + 1
return toRemove, good
}

View File

@@ -0,0 +1,100 @@
package ciphersuite
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"fmt"
"github.com/pion/dtls/v2/pkg/protocol"
"github.com/pion/dtls/v2/pkg/protocol/recordlayer"
)
const (
gcmTagLength = 16
gcmNonceLength = 12
)
// GCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets
type GCM struct {
localGCM, remoteGCM cipher.AEAD
localWriteIV, remoteWriteIV []byte
}
// NewGCM creates a DTLS GCM Cipher
func NewGCM(localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*GCM, error) {
localBlock, err := aes.NewCipher(localKey)
if err != nil {
return nil, err
}
localGCM, err := cipher.NewGCM(localBlock)
if err != nil {
return nil, err
}
remoteBlock, err := aes.NewCipher(remoteKey)
if err != nil {
return nil, err
}
remoteGCM, err := cipher.NewGCM(remoteBlock)
if err != nil {
return nil, err
}
return &GCM{
localGCM: localGCM,
localWriteIV: localWriteIV,
remoteGCM: remoteGCM,
remoteWriteIV: remoteWriteIV,
}, nil
}
// Encrypt encrypt a DTLS RecordLayer message
func (g *GCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
payload := raw[recordlayer.HeaderSize:]
raw = raw[:recordlayer.HeaderSize]
nonce := make([]byte, gcmNonceLength)
copy(nonce, g.localWriteIV[:4])
if _, err := rand.Read(nonce[4:]); err != nil {
return nil, err
}
additionalData := generateAEADAdditionalData(&pkt.Header, len(payload))
encryptedPayload := g.localGCM.Seal(nil, nonce, payload, additionalData)
r := make([]byte, len(raw)+len(nonce[4:])+len(encryptedPayload))
copy(r, raw)
copy(r[len(raw):], nonce[4:])
copy(r[len(raw)+len(nonce[4:]):], encryptedPayload)
// Update recordLayer size to include explicit nonce
binary.BigEndian.PutUint16(r[recordlayer.HeaderSize-2:], uint16(len(r)-recordlayer.HeaderSize))
return r, nil
}
// Decrypt decrypts a DTLS RecordLayer message
func (g *GCM) Decrypt(in []byte) ([]byte, error) {
var h recordlayer.Header
err := h.Unmarshal(in)
switch {
case err != nil:
return nil, err
case h.ContentType == protocol.ContentTypeChangeCipherSpec:
// Nothing to encrypt with ChangeCipherSpec
return in, nil
case len(in) <= (8 + recordlayer.HeaderSize):
return nil, errNotEnoughRoomForNonce
}
nonce := make([]byte, 0, gcmNonceLength)
nonce = append(append(nonce, g.remoteWriteIV[:4]...), in[recordlayer.HeaderSize:recordlayer.HeaderSize+8]...)
out := in[recordlayer.HeaderSize+8:]
additionalData := generateAEADAdditionalData(&h, len(out)-gcmTagLength)
out, err = g.remoteGCM.Open(out[:0], nonce, out, additionalData)
if err != nil {
return nil, fmt.Errorf("%w: %v", errDecryptPacket, err)
}
return append(in[:recordlayer.HeaderSize], out...), nil
}

View File

@@ -0,0 +1,22 @@
// Package clientcertificate provides all the support Client Certificate types
package clientcertificate
// Type is used to communicate what
// type of certificate is being transported
//
//https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-2
type Type byte
// ClientCertificateType enums
const (
RSASign Type = 1
ECDSASign Type = 64
)
// Types returns all valid ClientCertificate Types
func Types() map[Type]bool {
return map[Type]bool{
RSASign: true,
ECDSASign: true,
}
}

View File

@@ -0,0 +1,99 @@
// Package elliptic provides elliptic curve cryptography for DTLS
package elliptic
import (
"crypto/elliptic"
"crypto/rand"
"errors"
"golang.org/x/crypto/curve25519"
)
var errInvalidNamedCurve = errors.New("invalid named curve")
// CurvePointFormat is used to represent the IANA registered curve points
//
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-9
type CurvePointFormat byte
// CurvePointFormat enums
const (
CurvePointFormatUncompressed CurvePointFormat = 0
)
// Keypair is a Curve with a Private/Public Keypair
type Keypair struct {
Curve Curve
PublicKey []byte
PrivateKey []byte
}
// CurveType is used to represent the IANA registered curve types for TLS
//
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-10
type CurveType byte
// CurveType enums
const (
CurveTypeNamedCurve CurveType = 0x03
)
// CurveTypes returns all known curves
func CurveTypes() map[CurveType]struct{} {
return map[CurveType]struct{}{
CurveTypeNamedCurve: {},
}
}
// Curve is used to represent the IANA registered curves for TLS
//
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8
type Curve uint16
// Curve enums
const (
P256 Curve = 0x0017
P384 Curve = 0x0018
X25519 Curve = 0x001d
)
// Curves returns all curves we implement
func Curves() map[Curve]bool {
return map[Curve]bool{
X25519: true,
P256: true,
P384: true,
}
}
// GenerateKeypair generates a keypair for the given Curve
func GenerateKeypair(c Curve) (*Keypair, error) {
switch c { //nolint:golint
case X25519:
tmp := make([]byte, 32)
if _, err := rand.Read(tmp); err != nil {
return nil, err
}
var public, private [32]byte
copy(private[:], tmp)
curve25519.ScalarBaseMult(&public, &private)
return &Keypair{X25519, public[:], private[:]}, nil
case P256:
return ellipticCurveKeypair(P256, elliptic.P256(), elliptic.P256())
case P384:
return ellipticCurveKeypair(P384, elliptic.P384(), elliptic.P384())
default:
return nil, errInvalidNamedCurve
}
}
func ellipticCurveKeypair(nc Curve, c1, c2 elliptic.Curve) (*Keypair, error) {
privateKey, x, y, err := elliptic.GenerateKey(c1, rand.Reader)
if err != nil {
return nil, err
}
return &Keypair{nc, elliptic.Marshal(c2, x, y), privateKey}, nil
}

View File

@@ -0,0 +1,50 @@
// Package fingerprint provides a helper to create fingerprint string from certificate
package fingerprint
import (
"crypto"
"crypto/x509"
"errors"
"fmt"
)
var (
errHashUnavailable = errors.New("fingerprint: hash algorithm is not linked into the binary")
errInvalidFingerprintLength = errors.New("fingerprint: invalid fingerprint length")
)
// Fingerprint creates a fingerprint for a certificate using the specified hash algorithm
func Fingerprint(cert *x509.Certificate, algo crypto.Hash) (string, error) {
if !algo.Available() {
return "", errHashUnavailable
}
h := algo.New()
for i := 0; i < len(cert.Raw); {
n, _ := h.Write(cert.Raw[i:])
// Hash.Writer is specified to be never returning an error.
// https://golang.org/pkg/hash/#Hash
i += n
}
digest := []byte(fmt.Sprintf("%x", h.Sum(nil)))
digestlen := len(digest)
if digestlen == 0 {
return "", nil
}
if digestlen%2 != 0 {
return "", errInvalidFingerprintLength
}
res := make([]byte, digestlen>>1+digestlen-1)
pos := 0
for i, c := range digest {
res[pos] = c
pos++
if (i)%2 != 0 && i < digestlen-1 {
res[pos] = byte(':')
pos++
}
}
return string(res), nil
}

View File

@@ -0,0 +1,52 @@
package fingerprint
import (
"crypto"
"crypto/x509"
"errors"
"testing"
)
var errInvalidHashID = errors.New("invalid hash ID")
func TestFingerprint(t *testing.T) {
rawCertificate := []byte{
0x30, 0x82, 0x01, 0x98, 0x30, 0x82, 0x01, 0x3d, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x11, 0x00, 0xa9, 0x91, 0x76, 0x0a, 0xcd, 0x97, 0x4c, 0x36, 0xba,
0xc9, 0xc2, 0x66, 0x91, 0x47, 0x6c, 0xac, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x2b, 0x31, 0x29, 0x30, 0x27,
0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x31, 0x31, 0x30, 0x30,
0x39, 0x30, 0x34, 0x32, 0x33, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x31, 0x30, 0x30, 0x39, 0x30, 0x34, 0x32, 0x33, 0x5a, 0x30, 0x2b, 0x31, 0x29,
0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48,
0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x9c, 0x12, 0x8e, 0xb5, 0x21, 0x23, 0x9f,
0x35, 0x5d, 0x39, 0x64, 0xc3, 0x75, 0x81, 0xa4, 0xc8, 0xc8, 0x08, 0x8a, 0xa8, 0x42, 0x30, 0x30, 0x65, 0xb8, 0xb1, 0x3e, 0x4a, 0x51, 0x86, 0xeb, 0xad,
0x03, 0x02, 0x35, 0x83, 0xc4, 0x19, 0x3a, 0x5b, 0x79, 0x83, 0xec, 0x59, 0x0e, 0x4f, 0x99, 0xb1, 0xd2, 0xf0, 0x50, 0xfa, 0xb8, 0x5f, 0xfc, 0x88, 0xf3,
0x15, 0xed, 0xb8, 0x14, 0xf0, 0xba, 0xcd, 0xa3, 0x42, 0x30, 0x40, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
0x05, 0xa0, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08,
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff,
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x49, 0x00, 0x30, 0x46, 0x02, 0x21, 0x00, 0xcd, 0x44, 0xb1, 0xf2, 0x09,
0xe5, 0xf1, 0xf4, 0xc9, 0x26, 0x95, 0x9a, 0x2d, 0x6d, 0xf3, 0x0c, 0xb8, 0xeb, 0x27, 0x2d, 0x81, 0x19, 0xe9, 0x51, 0xf7, 0xad, 0x64, 0x7d, 0x42, 0x32,
0x9e, 0xf8, 0x02, 0x21, 0x00, 0xee, 0xad, 0x96, 0x41, 0xf1, 0x12, 0xd0, 0x6b, 0xcd, 0x09, 0xf0, 0x3c, 0x67, 0xb3, 0xdd, 0xed, 0x0a, 0xf1, 0xd8, 0x41,
0x4f, 0x61, 0xfd, 0x53, 0x1d, 0xf5, 0x27, 0xbe, 0x6d, 0x0b, 0xe2, 0x0d,
}
cert, err := x509.ParseCertificate(rawCertificate)
if err != nil {
t.Fatal(err)
}
const expectedSHA256 = "60:ef:f5:79:ad:8d:3e:d7:e8:4d:5a:5a:d6:1e:71:2d:47:52:a5:cb:df:34:37:87:10:a5:4e:d7:2a:2c:37:34"
actualSHA256, err := Fingerprint(cert, crypto.SHA256)
if err != nil {
t.Fatal(err)
} else if actualSHA256 != expectedSHA256 {
t.Fatalf("Fingerprint SHA256 mismatch expected(%s) actual(%s)", expectedSHA256, actualSHA256)
}
}
func TestFingerprint_UnavailableHash(t *testing.T) {
_, err := Fingerprint(&x509.Certificate{}, crypto.Hash(0xFFFFFFFF))
if !errors.Is(err, errHashUnavailable) {
t.Errorf("%w: Expected error '%v' for invalid hash ID, got '%v'", errInvalidHashID, errHashUnavailable, err)
}
}

View File

@@ -0,0 +1,37 @@
package fingerprint
import (
"crypto"
"errors"
)
var errInvalidHashAlgorithm = errors.New("fingerprint: invalid hash algorithm")
func nameToHash() map[string]crypto.Hash {
return map[string]crypto.Hash{
"md5": crypto.MD5, // [RFC3279]
"sha-1": crypto.SHA1, // [RFC3279]
"sha-224": crypto.SHA224, // [RFC4055]
"sha-256": crypto.SHA256, // [RFC4055]
"sha-384": crypto.SHA384, // [RFC4055]
"sha-512": crypto.SHA512, // [RFC4055]
}
}
// HashFromString allows looking up a hash algorithm by it's string representation
func HashFromString(s string) (crypto.Hash, error) {
if h, ok := nameToHash()[s]; ok {
return h, nil
}
return 0, errInvalidHashAlgorithm
}
// StringFromHash allows looking up a string representation of the crypto.Hash.
func StringFromHash(hash crypto.Hash) (string, error) {
for s, h := range nameToHash() {
if h == hash {
return s, nil
}
}
return "", errInvalidHashAlgorithm
}

View File

@@ -0,0 +1,41 @@
package fingerprint
import (
"crypto"
"errors"
"testing"
)
func TestHashFromString(t *testing.T) {
t.Run("InvalidHashAlgorithm", func(t *testing.T) {
_, err := HashFromString("invalid-hash-algorithm")
if !errors.Is(err, errInvalidHashAlgorithm) {
t.Errorf("Expected error '%v' for invalid hash name, got '%v'", errInvalidHashAlgorithm, err)
}
})
t.Run("ValidHashAlgorithm", func(t *testing.T) {
h, err := HashFromString("sha-512")
if err != nil {
t.Fatalf("Unexpected error for valid hash name, got '%v'", err)
}
if h != crypto.SHA512 {
t.Errorf("Expected hash ID of %d, got %d", int(crypto.SHA512), int(h))
}
})
}
func TestStringFromHash_Roundtrip(t *testing.T) {
for _, h := range nameToHash() {
s, err := StringFromHash(h)
if err != nil {
t.Fatalf("Unexpected error for valid hash algorithm, got '%v'", err)
}
h2, err := HashFromString(s)
if err != nil {
t.Fatalf("Unexpected error for valid hash name, got '%v'", err)
}
if h != h2 {
t.Errorf("Hash value doesn't match, expected: 0x%x, got 0x%x", h, h2)
}
}
}

View File

@@ -0,0 +1,126 @@
// Package hash provides TLS HashAlgorithm as defined in TLS 1.2
package hash
import ( //nolint:gci
"crypto"
"crypto/md5" //nolint:gosec
"crypto/sha1" //nolint:gosec
"crypto/sha256"
"crypto/sha512"
)
// Algorithm is used to indicate the hash algorithm used
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18
type Algorithm uint16
// Supported hash algorithms
const (
None Algorithm = 0 // Blacklisted
MD5 Algorithm = 1 // Blacklisted
SHA1 Algorithm = 2 // Blacklisted
SHA224 Algorithm = 3
SHA256 Algorithm = 4
SHA384 Algorithm = 5
SHA512 Algorithm = 6
Ed25519 Algorithm = 8
)
// String makes hashAlgorithm printable
func (a Algorithm) String() string {
switch a {
case None:
return "none"
case MD5:
return "md5" // [RFC3279]
case SHA1:
return "sha-1" // [RFC3279]
case SHA224:
return "sha-224" // [RFC4055]
case SHA256:
return "sha-256" // [RFC4055]
case SHA384:
return "sha-384" // [RFC4055]
case SHA512:
return "sha-512" // [RFC4055]
case Ed25519:
return "null"
default:
return "unknown or unsupported hash algorithm"
}
}
// Digest performs a digest on the passed value
func (a Algorithm) Digest(b []byte) []byte {
switch a {
case None:
return nil
case MD5:
hash := md5.Sum(b) // #nosec
return hash[:]
case SHA1:
hash := sha1.Sum(b) // #nosec
return hash[:]
case SHA224:
hash := sha256.Sum224(b)
return hash[:]
case SHA256:
hash := sha256.Sum256(b)
return hash[:]
case SHA384:
hash := sha512.Sum384(b)
return hash[:]
case SHA512:
hash := sha512.Sum512(b)
return hash[:]
default:
return nil
}
}
// Insecure returns if the given HashAlgorithm is considered secure in DTLS 1.2
func (a Algorithm) Insecure() bool {
switch a {
case None, MD5, SHA1:
return true
default:
return false
}
}
// CryptoHash returns the crypto.Hash implementation for the given HashAlgorithm
func (a Algorithm) CryptoHash() crypto.Hash {
switch a {
case None:
return crypto.Hash(0)
case MD5:
return crypto.MD5
case SHA1:
return crypto.SHA1
case SHA224:
return crypto.SHA224
case SHA256:
return crypto.SHA256
case SHA384:
return crypto.SHA384
case SHA512:
return crypto.SHA512
case Ed25519:
return crypto.Hash(0)
default:
return crypto.Hash(0)
}
}
// Algorithms returns all the supported Hash Algorithms
func Algorithms() map[Algorithm]struct{} {
return map[Algorithm]struct{}{
None: {},
MD5: {},
SHA1: {},
SHA224: {},
SHA256: {},
SHA384: {},
SHA512: {},
Ed25519: {},
}
}

View File

@@ -0,0 +1,25 @@
package hash
import (
"testing"
"github.com/pion/dtls/v2/pkg/crypto/fingerprint"
)
func TestHashAlgorithm_StringRoundtrip(t *testing.T) {
for algo := range Algorithms() {
if algo == Ed25519 || algo == None {
continue
}
str := algo.String()
hash1 := algo.CryptoHash()
hash2, err := fingerprint.HashFromString(str)
if err != nil {
t.Fatalf("fingerprint.HashFromString failed: %v", err)
}
if hash1 != hash2 {
t.Errorf("Hash algorithm mismatch, input: %d, after roundtrip: %d", int(hash1), int(hash2))
}
}
}

View File

@@ -0,0 +1,224 @@
// Package prf implements TLS 1.2 Pseudorandom functions
package prf
import ( //nolint:gci
ellipticStdlib "crypto/elliptic"
"crypto/hmac"
"encoding/binary"
"errors"
"fmt"
"hash"
"math"
"github.com/pion/dtls/v2/pkg/crypto/elliptic"
"github.com/pion/dtls/v2/pkg/protocol"
"golang.org/x/crypto/curve25519"
)
const (
masterSecretLabel = "master secret"
extendedMasterSecretLabel = "extended master secret"
keyExpansionLabel = "key expansion"
verifyDataClientLabel = "client finished"
verifyDataServerLabel = "server finished"
)
// HashFunc allows callers to decide what hash is used in PRF
type HashFunc func() hash.Hash
// EncryptionKeys is all the state needed for a TLS CipherSuite
type EncryptionKeys struct {
MasterSecret []byte
ClientMACKey []byte
ServerMACKey []byte
ClientWriteKey []byte
ServerWriteKey []byte
ClientWriteIV []byte
ServerWriteIV []byte
}
var errInvalidNamedCurve = &protocol.FatalError{Err: errors.New("invalid named curve")} //nolint:goerr113
func (e *EncryptionKeys) String() string {
return fmt.Sprintf(`encryptionKeys:
- masterSecret: %#v
- clientMACKey: %#v
- serverMACKey: %#v
- clientWriteKey: %#v
- serverWriteKey: %#v
- clientWriteIV: %#v
- serverWriteIV: %#v
`,
e.MasterSecret,
e.ClientMACKey,
e.ServerMACKey,
e.ClientWriteKey,
e.ServerWriteKey,
e.ClientWriteIV,
e.ServerWriteIV)
}
// PSKPreMasterSecret generates the PSK Premaster Secret
// The premaster secret is formed as follows: if the PSK is N octets
// long, concatenate a uint16 with the value N, N zero octets, a second
// uint16 with the value N, and the PSK itself.
//
// https://tools.ietf.org/html/rfc4279#section-2
func PSKPreMasterSecret(psk []byte) []byte {
pskLen := uint16(len(psk))
out := append(make([]byte, 2+pskLen+2), psk...)
binary.BigEndian.PutUint16(out, pskLen)
binary.BigEndian.PutUint16(out[2+pskLen:], pskLen)
return out
}
// PreMasterSecret implements TLS 1.2 Premaster Secret generation given a keypair and a curve
func PreMasterSecret(publicKey, privateKey []byte, curve elliptic.Curve) ([]byte, error) {
switch curve {
case elliptic.X25519:
return curve25519.X25519(privateKey, publicKey)
case elliptic.P256:
return ellipticCurvePreMasterSecret(publicKey, privateKey, ellipticStdlib.P256(), ellipticStdlib.P256())
case elliptic.P384:
return ellipticCurvePreMasterSecret(publicKey, privateKey, ellipticStdlib.P384(), ellipticStdlib.P384())
default:
return nil, errInvalidNamedCurve
}
}
func ellipticCurvePreMasterSecret(publicKey, privateKey []byte, c1, c2 ellipticStdlib.Curve) ([]byte, error) {
x, y := ellipticStdlib.Unmarshal(c1, publicKey)
if x == nil || y == nil {
return nil, errInvalidNamedCurve
}
result, _ := c2.ScalarMult(x, y, privateKey)
preMasterSecret := make([]byte, (c2.Params().BitSize+7)>>3)
resultBytes := result.Bytes()
copy(preMasterSecret[len(preMasterSecret)-len(resultBytes):], resultBytes)
return preMasterSecret, nil
}
// PHash is PRF is the SHA-256 hash function is used for all cipher suites
// defined in this TLS 1.2 document and in TLS documents published prior to this
// document when TLS 1.2 is negotiated. New cipher suites MUST explicitly
// specify a PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a
// stronger standard hash function.
//
// P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
// HMAC_hash(secret, A(2) + seed) +
// HMAC_hash(secret, A(3) + seed) + ...
//
// A() is defined as:
//
// A(0) = seed
// A(i) = HMAC_hash(secret, A(i-1))
//
// P_hash can be iterated as many times as necessary to produce the
// required quantity of data. For example, if P_SHA256 is being used to
// create 80 bytes of data, it will have to be iterated three times
// (through A(3)), creating 96 bytes of output data; the last 16 bytes
// of the final iteration will then be discarded, leaving 80 bytes of
// output data.
//
// https://tools.ietf.org/html/rfc4346w
func PHash(secret, seed []byte, requestedLength int, h HashFunc) ([]byte, error) {
hmacSHA256 := func(key, data []byte) ([]byte, error) {
mac := hmac.New(h, key)
if _, err := mac.Write(data); err != nil {
return nil, err
}
return mac.Sum(nil), nil
}
var err error
lastRound := seed
out := []byte{}
iterations := int(math.Ceil(float64(requestedLength) / float64(h().Size())))
for i := 0; i < iterations; i++ {
lastRound, err = hmacSHA256(secret, lastRound)
if err != nil {
return nil, err
}
withSecret, err := hmacSHA256(secret, append(lastRound, seed...))
if err != nil {
return nil, err
}
out = append(out, withSecret...)
}
return out[:requestedLength], nil
}
// ExtendedMasterSecret generates a Extended MasterSecret as defined in
// https://tools.ietf.org/html/rfc7627
func ExtendedMasterSecret(preMasterSecret, sessionHash []byte, h HashFunc) ([]byte, error) {
seed := append([]byte(extendedMasterSecretLabel), sessionHash...)
return PHash(preMasterSecret, seed, 48, h)
}
// MasterSecret generates a TLS 1.2 MasterSecret
func MasterSecret(preMasterSecret, clientRandom, serverRandom []byte, h HashFunc) ([]byte, error) {
seed := append(append([]byte(masterSecretLabel), clientRandom...), serverRandom...)
return PHash(preMasterSecret, seed, 48, h)
}
// GenerateEncryptionKeys is the final step TLS 1.2 PRF. Given all state generated so far generates
// the final keys need for encryption
func GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int, h HashFunc) (*EncryptionKeys, error) {
seed := append(append([]byte(keyExpansionLabel), serverRandom...), clientRandom...)
keyMaterial, err := PHash(masterSecret, seed, (2*macLen)+(2*keyLen)+(2*ivLen), h)
if err != nil {
return nil, err
}
clientMACKey := keyMaterial[:macLen]
keyMaterial = keyMaterial[macLen:]
serverMACKey := keyMaterial[:macLen]
keyMaterial = keyMaterial[macLen:]
clientWriteKey := keyMaterial[:keyLen]
keyMaterial = keyMaterial[keyLen:]
serverWriteKey := keyMaterial[:keyLen]
keyMaterial = keyMaterial[keyLen:]
clientWriteIV := keyMaterial[:ivLen]
keyMaterial = keyMaterial[ivLen:]
serverWriteIV := keyMaterial[:ivLen]
return &EncryptionKeys{
MasterSecret: masterSecret,
ClientMACKey: clientMACKey,
ServerMACKey: serverMACKey,
ClientWriteKey: clientWriteKey,
ServerWriteKey: serverWriteKey,
ClientWriteIV: clientWriteIV,
ServerWriteIV: serverWriteIV,
}, nil
}
func prfVerifyData(masterSecret, handshakeBodies []byte, label string, hashFunc HashFunc) ([]byte, error) {
h := hashFunc()
if _, err := h.Write(handshakeBodies); err != nil {
return nil, err
}
seed := append([]byte(label), h.Sum(nil)...)
return PHash(masterSecret, seed, 12, hashFunc)
}
// VerifyDataClient is caled on the Client Side to either verify or generate the VerifyData message
func VerifyDataClient(masterSecret, handshakeBodies []byte, h HashFunc) ([]byte, error) {
return prfVerifyData(masterSecret, handshakeBodies, verifyDataClientLabel, h)
}
// VerifyDataServer is caled on the Server Side to either verify or generate the VerifyData message
func VerifyDataServer(masterSecret, handshakeBodies []byte, h HashFunc) ([]byte, error) {
return prfVerifyData(masterSecret, handshakeBodies, verifyDataServerLabel, h)
}

View File

@@ -0,0 +1,80 @@
package prf
import (
"bytes"
"crypto/sha256"
"reflect"
"testing"
"github.com/pion/dtls/v2/pkg/crypto/elliptic"
)
func TestPreMasterSecret(t *testing.T) {
privateKey := []byte{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f}
publicKey := []byte{0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, 0xf9, 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, 0xb6, 0x15}
expectedPreMasterSecret := []byte{0xdf, 0x4a, 0x29, 0x1b, 0xaa, 0x1e, 0xb7, 0xcf, 0xa6, 0x93, 0x4b, 0x29, 0xb4, 0x74, 0xba, 0xad, 0x26, 0x97, 0xe2, 0x9f, 0x1f, 0x92, 0x0d, 0xcc, 0x77, 0xc8, 0xa0, 0xa0, 0x88, 0x44, 0x76, 0x24}
preMasterSecret, err := PreMasterSecret(publicKey, privateKey, elliptic.X25519)
if err != nil {
t.Fatal(err)
} else if !bytes.Equal(expectedPreMasterSecret, preMasterSecret) {
t.Fatalf("PremasterSecret exp: % 02x actual: % 02x", expectedPreMasterSecret, preMasterSecret)
}
}
func TestMasterSecret(t *testing.T) {
preMasterSecret := []byte{0xdf, 0x4a, 0x29, 0x1b, 0xaa, 0x1e, 0xb7, 0xcf, 0xa6, 0x93, 0x4b, 0x29, 0xb4, 0x74, 0xba, 0xad, 0x26, 0x97, 0xe2, 0x9f, 0x1f, 0x92, 0x0d, 0xcc, 0x77, 0xc8, 0xa0, 0xa0, 0x88, 0x44, 0x76, 0x24}
clientRandom := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
serverRandom := []byte{0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f}
expectedMasterSecret := []byte{0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, 0x37, 0x34, 0x4c}
masterSecret, err := MasterSecret(preMasterSecret, clientRandom, serverRandom, sha256.New)
if err != nil {
t.Fatal(err)
} else if !bytes.Equal(expectedMasterSecret, masterSecret) {
t.Fatalf("masterSecret exp: % 02x actual: % 02x", expectedMasterSecret, masterSecret)
}
}
func TestEncryptionKeys(t *testing.T) {
clientRandom := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
serverRandom := []byte{0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f}
masterSecret := []byte{0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, 0x37, 0x34, 0x4c}
expectedEncryptionKeys := &EncryptionKeys{
MasterSecret: masterSecret,
ClientMACKey: []byte{},
ServerMACKey: []byte{},
ClientWriteKey: []byte{0x1b, 0x7d, 0x11, 0x7c, 0x7d, 0x5f, 0x69, 0x0b, 0xc2, 0x63, 0xca, 0xe8, 0xef, 0x60, 0xaf, 0x0f},
ServerWriteKey: []byte{0x18, 0x78, 0xac, 0xc2, 0x2a, 0xd8, 0xbd, 0xd8, 0xc6, 0x01, 0xa6, 0x17, 0x12, 0x6f, 0x63, 0x54},
ClientWriteIV: []byte{0x0e, 0xb2, 0x09, 0x06},
ServerWriteIV: []byte{0xf7, 0x81, 0xfa, 0xd2},
}
keys, err := GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, 0, 16, 4, sha256.New)
if err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(expectedEncryptionKeys, keys) {
t.Fatalf("masterSecret exp: %q actual: %q", expectedEncryptionKeys, keys)
}
}
func TestVerifyData(t *testing.T) {
clientHello := []byte{0x01, 0x00, 0x00, 0xa1, 0x03, 0x03, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x00, 0x20, 0xcc, 0xa8, 0xcc, 0xa9, 0xc0, 0x2f, 0xc0, 0x30, 0xc0, 0x2b, 0xc0, 0x2c, 0xc0, 0x13, 0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x0a, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0xc0, 0x12, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 0x13, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x75, 0x6c, 0x66, 0x68, 0x65, 0x69, 0x6d, 0x2e, 0x6e, 0x65, 0x74, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x01, 0x04, 0x03, 0x05, 0x01, 0x05, 0x03, 0x06, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02, 0x03, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00}
serverHello := []byte{0x02, 0x00, 0x00, 0x2d, 0x03, 0x03, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x00, 0xc0, 0x13, 0x00, 0x00, 0x05, 0xff, 0x01, 0x00, 0x01, 0x00}
serverCertificate := []byte{0x0b, 0x00, 0x03, 0x2b, 0x00, 0x03, 0x28, 0x00, 0x03, 0x25, 0x30, 0x82, 0x03, 0x21, 0x30, 0x82, 0x02, 0x09, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x15, 0x5a, 0x92, 0xad, 0xc2, 0x04, 0x8f, 0x90, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x22, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38, 0x31, 0x30, 0x30, 0x35, 0x30, 0x31, 0x33, 0x38, 0x31, 0x37, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x30, 0x30, 0x35, 0x30, 0x31, 0x33, 0x38, 0x31, 0x37, 0x5a, 0x30, 0x2b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x13, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x75, 0x6c, 0x66, 0x68, 0x65, 0x69, 0x6d, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc4, 0x80, 0x36, 0x06, 0xba, 0xe7, 0x47, 0x6b, 0x08, 0x94, 0x04, 0xec, 0xa7, 0xb6, 0x91, 0x04, 0x3f, 0xf7, 0x92, 0xbc, 0x19, 0xee, 0xfb, 0x7d, 0x74, 0xd7, 0xa8, 0x0d, 0x00, 0x1e, 0x7b, 0x4b, 0x3a, 0x4a, 0xe6, 0x0f, 0xe8, 0xc0, 0x71, 0xfc, 0x73, 0xe7, 0x02, 0x4c, 0x0d, 0xbc, 0xf4, 0xbd, 0xd1, 0x1d, 0x39, 0x6b, 0xba, 0x70, 0x46, 0x4a, 0x13, 0xe9, 0x4a, 0xf8, 0x3d, 0xf3, 0xe1, 0x09, 0x59, 0x54, 0x7b, 0xc9, 0x55, 0xfb, 0x41, 0x2d, 0xa3, 0x76, 0x52, 0x11, 0xe1, 0xf3, 0xdc, 0x77, 0x6c, 0xaa, 0x53, 0x37, 0x6e, 0xca, 0x3a, 0xec, 0xbe, 0xc3, 0xaa, 0xb7, 0x3b, 0x31, 0xd5, 0x6c, 0xb6, 0x52, 0x9c, 0x80, 0x98, 0xbc, 0xc9, 0xe0, 0x28, 0x18, 0xe2, 0x0b, 0xf7, 0xf8, 0xa0, 0x3a, 0xfd, 0x17, 0x04, 0x50, 0x9e, 0xce, 0x79, 0xbd, 0x9f, 0x39, 0xf1, 0xea, 0x69, 0xec, 0x47, 0x97, 0x2e, 0x83, 0x0f, 0xb5, 0xca, 0x95, 0xde, 0x95, 0xa1, 0xe6, 0x04, 0x22, 0xd5, 0xee, 0xbe, 0x52, 0x79, 0x54, 0xa1, 0xe7, 0xbf, 0x8a, 0x86, 0xf6, 0x46, 0x6d, 0x0d, 0x9f, 0x16, 0x95, 0x1a, 0x4c, 0xf7, 0xa0, 0x46, 0x92, 0x59, 0x5c, 0x13, 0x52, 0xf2, 0x54, 0x9e, 0x5a, 0xfb, 0x4e, 0xbf, 0xd7, 0x7a, 0x37, 0x95, 0x01, 0x44, 0xe4, 0xc0, 0x26, 0x87, 0x4c, 0x65, 0x3e, 0x40, 0x7d, 0x7d, 0x23, 0x07, 0x44, 0x01, 0xf4, 0x84, 0xff, 0xd0, 0x8f, 0x7a, 0x1f, 0xa0, 0x52, 0x10, 0xd1, 0xf4, 0xf0, 0xd5, 0xce, 0x79, 0x70, 0x29, 0x32, 0xe2, 0xca, 0xbe, 0x70, 0x1f, 0xdf, 0xad, 0x6b, 0x4b, 0xb7, 0x11, 0x01, 0xf4, 0x4b, 0xad, 0x66, 0x6a, 0x11, 0x13, 0x0f, 0xe2, 0xee, 0x82, 0x9e, 0x4d, 0x02, 0x9d, 0xc9, 0x1c, 0xdd, 0x67, 0x16, 0xdb, 0xb9, 0x06, 0x18, 0x86, 0xed, 0xc1, 0xba, 0x94, 0x21, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x52, 0x30, 0x50, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x89, 0x4f, 0xde, 0x5b, 0xcc, 0x69, 0xe2, 0x52, 0xcf, 0x3e, 0xa3, 0x00, 0xdf, 0xb1, 0x97, 0xb8, 0x1d, 0xe1, 0xc1, 0x46, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x59, 0x16, 0x45, 0xa6, 0x9a, 0x2e, 0x37, 0x79, 0xe4, 0xf6, 0xdd, 0x27, 0x1a, 0xba, 0x1c, 0x0b, 0xfd, 0x6c, 0xd7, 0x55, 0x99, 0xb5, 0xe7, 0xc3, 0x6e, 0x53, 0x3e, 0xff, 0x36, 0x59, 0x08, 0x43, 0x24, 0xc9, 0xe7, 0xa5, 0x04, 0x07, 0x9d, 0x39, 0xe0, 0xd4, 0x29, 0x87, 0xff, 0xe3, 0xeb, 0xdd, 0x09, 0xc1, 0xcf, 0x1d, 0x91, 0x44, 0x55, 0x87, 0x0b, 0x57, 0x1d, 0xd1, 0x9b, 0xdf, 0x1d, 0x24, 0xf8, 0xbb, 0x9a, 0x11, 0xfe, 0x80, 0xfd, 0x59, 0x2b, 0xa0, 0x39, 0x8c, 0xde, 0x11, 0xe2, 0x65, 0x1e, 0x61, 0x8c, 0xe5, 0x98, 0xfa, 0x96, 0xe5, 0x37, 0x2e, 0xef, 0x3d, 0x24, 0x8a, 0xfd, 0xe1, 0x74, 0x63, 0xeb, 0xbf, 0xab, 0xb8, 0xe4, 0xd1, 0xab, 0x50, 0x2a, 0x54, 0xec, 0x00, 0x64, 0xe9, 0x2f, 0x78, 0x19, 0x66, 0x0d, 0x3f, 0x27, 0xcf, 0x20, 0x9e, 0x66, 0x7f, 0xce, 0x5a, 0xe2, 0xe4, 0xac, 0x99, 0xc7, 0xc9, 0x38, 0x18, 0xf8, 0xb2, 0x51, 0x07, 0x22, 0xdf, 0xed, 0x97, 0xf3, 0x2e, 0x3e, 0x93, 0x49, 0xd4, 0xc6, 0x6c, 0x9e, 0xa6, 0x39, 0x6d, 0x74, 0x44, 0x62, 0xa0, 0x6b, 0x42, 0xc6, 0xd5, 0xba, 0x68, 0x8e, 0xac, 0x3a, 0x01, 0x7b, 0xdd, 0xfc, 0x8e, 0x2c, 0xfc, 0xad, 0x27, 0xcb, 0x69, 0xd3, 0xcc, 0xdc, 0xa2, 0x80, 0x41, 0x44, 0x65, 0xd3, 0xae, 0x34, 0x8c, 0xe0, 0xf3, 0x4a, 0xb2, 0xfb, 0x9c, 0x61, 0x83, 0x71, 0x31, 0x2b, 0x19, 0x10, 0x41, 0x64, 0x1c, 0x23, 0x7f, 0x11, 0xa5, 0xd6, 0x5c, 0x84, 0x4f, 0x04, 0x04, 0x84, 0x99, 0x38, 0x71, 0x2b, 0x95, 0x9e, 0xd6, 0x85, 0xbc, 0x5c, 0x5d, 0xd6, 0x45, 0xed, 0x19, 0x90, 0x94, 0x73, 0x40, 0x29, 0x26, 0xdc, 0xb4, 0x0e, 0x34, 0x69, 0xa1, 0x59, 0x41, 0xe8, 0xe2, 0xcc, 0xa8, 0x4b, 0xb6, 0x08, 0x46, 0x36, 0xa0}
serverKeyExchange := []byte{0x0c, 0x00, 0x01, 0x28, 0x03, 0x00, 0x1d, 0x20, 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, 0xf9, 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, 0xb6, 0x15, 0x04, 0x01, 0x01, 0x00, 0x04, 0x02, 0xb6, 0x61, 0xf7, 0xc1, 0x91, 0xee, 0x59, 0xbe, 0x45, 0x37, 0x66, 0x39, 0xbd, 0xc3, 0xd4, 0xbb, 0x81, 0xe1, 0x15, 0xca, 0x73, 0xc8, 0x34, 0x8b, 0x52, 0x5b, 0x0d, 0x23, 0x38, 0xaa, 0x14, 0x46, 0x67, 0xed, 0x94, 0x31, 0x02, 0x14, 0x12, 0xcd, 0x9b, 0x84, 0x4c, 0xba, 0x29, 0x93, 0x4a, 0xaa, 0xcc, 0xe8, 0x73, 0x41, 0x4e, 0xc1, 0x1c, 0xb0, 0x2e, 0x27, 0x2d, 0x0a, 0xd8, 0x1f, 0x76, 0x7d, 0x33, 0x07, 0x67, 0x21, 0xf1, 0x3b, 0xf3, 0x60, 0x20, 0xcf, 0x0b, 0x1f, 0xd0, 0xec, 0xb0, 0x78, 0xde, 0x11, 0x28, 0xbe, 0xba, 0x09, 0x49, 0xeb, 0xec, 0xe1, 0xa1, 0xf9, 0x6e, 0x20, 0x9d, 0xc3, 0x6e, 0x4f, 0xff, 0xd3, 0x6b, 0x67, 0x3a, 0x7d, 0xdc, 0x15, 0x97, 0xad, 0x44, 0x08, 0xe4, 0x85, 0xc4, 0xad, 0xb2, 0xc8, 0x73, 0x84, 0x12, 0x49, 0x37, 0x25, 0x23, 0x80, 0x9e, 0x43, 0x12, 0xd0, 0xc7, 0xb3, 0x52, 0x2e, 0xf9, 0x83, 0xca, 0xc1, 0xe0, 0x39, 0x35, 0xff, 0x13, 0xa8, 0xe9, 0x6b, 0xa6, 0x81, 0xa6, 0x2e, 0x40, 0xd3, 0xe7, 0x0a, 0x7f, 0xf3, 0x58, 0x66, 0xd3, 0xd9, 0x99, 0x3f, 0x9e, 0x26, 0xa6, 0x34, 0xc8, 0x1b, 0x4e, 0x71, 0x38, 0x0f, 0xcd, 0xd6, 0xf4, 0xe8, 0x35, 0xf7, 0x5a, 0x64, 0x09, 0xc7, 0xdc, 0x2c, 0x07, 0x41, 0x0e, 0x6f, 0x87, 0x85, 0x8c, 0x7b, 0x94, 0xc0, 0x1c, 0x2e, 0x32, 0xf2, 0x91, 0x76, 0x9e, 0xac, 0xca, 0x71, 0x64, 0x3b, 0x8b, 0x98, 0xa9, 0x63, 0xdf, 0x0a, 0x32, 0x9b, 0xea, 0x4e, 0xd6, 0x39, 0x7e, 0x8c, 0xd0, 0x1a, 0x11, 0x0a, 0xb3, 0x61, 0xac, 0x5b, 0xad, 0x1c, 0xcd, 0x84, 0x0a, 0x6c, 0x8a, 0x6e, 0xaa, 0x00, 0x1a, 0x9d, 0x7d, 0x87, 0xdc, 0x33, 0x18, 0x64, 0x35, 0x71, 0x22, 0x6c, 0x4d, 0xd2, 0xc2, 0xac, 0x41, 0xfb}
serverHelloDone := []byte{0x0e, 0x00, 0x00, 0x00}
clientKeyExchange := []byte{0x10, 0x00, 0x00, 0x21, 0x20, 0x35, 0x80, 0x72, 0xd6, 0x36, 0x58, 0x80, 0xd1, 0xae, 0xea, 0x32, 0x9a, 0xdf, 0x91, 0x21, 0x38, 0x38, 0x51, 0xed, 0x21, 0xa2, 0x8e, 0x3b, 0x75, 0xe9, 0x65, 0xd0, 0xd2, 0xcd, 0x16, 0x62, 0x54}
finalMsg := append(append(append(append(append(clientHello, serverHello...), serverCertificate...), serverKeyExchange...), serverHelloDone...), clientKeyExchange...)
masterSecret := []byte{0x91, 0x6a, 0xbf, 0x9d, 0xa5, 0x59, 0x73, 0xe1, 0x36, 0x14, 0xae, 0x0a, 0x3f, 0x5d, 0x3f, 0x37, 0xb0, 0x23, 0xba, 0x12, 0x9a, 0xee, 0x02, 0xcc, 0x91, 0x34, 0x33, 0x81, 0x27, 0xcd, 0x70, 0x49, 0x78, 0x1c, 0x8e, 0x19, 0xfc, 0x1e, 0xb2, 0xa7, 0x38, 0x7a, 0xc0, 0x6a, 0xe2, 0x37, 0x34, 0x4c}
expectedVerifyData := []byte{0xcf, 0x91, 0x96, 0x26, 0xf1, 0x36, 0x0c, 0x53, 0x6a, 0xaa, 0xd7, 0x3a}
verifyData, err := VerifyDataClient(masterSecret, finalMsg, sha256.New)
if err != nil {
t.Fatal(err)
} else if !bytes.Equal(expectedVerifyData, verifyData) {
t.Fatalf("verifyData exp: %q actual: %q", expectedVerifyData, verifyData)
}
}

View File

@@ -0,0 +1,97 @@
// Package selfsign is a test helper that generates self signed certificate.
package selfsign
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"math/big"
"time"
)
var errInvalidPrivateKey = errors.New("selfsign: invalid private key type")
// GenerateSelfSigned creates a self-signed certificate
func GenerateSelfSigned() (tls.Certificate, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
return SelfSign(priv)
}
// GenerateSelfSignedWithDNS creates a self-signed certificate
func GenerateSelfSignedWithDNS(cn string, sans ...string) (tls.Certificate, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
}
return WithDNS(priv, cn, sans...)
}
// SelfSign creates a self-signed certificate from a elliptic curve key
func SelfSign(key crypto.PrivateKey) (tls.Certificate, error) {
return WithDNS(key, hex.EncodeToString(make([]byte, 16)))
}
// WithDNS creates a self-signed certificate from a elliptic curve key
func WithDNS(key crypto.PrivateKey, cn string, sans ...string) (tls.Certificate, error) {
var (
pubKey crypto.PublicKey
maxBigInt = new(big.Int) // Max random value, a 130-bits integer, i.e 2^130 - 1
)
switch k := key.(type) {
case ed25519.PrivateKey:
pubKey = k.Public()
case *ecdsa.PrivateKey:
pubKey = k.Public()
default:
return tls.Certificate{}, errInvalidPrivateKey
}
/* #nosec */
maxBigInt.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(maxBigInt, big.NewInt(1))
/* #nosec */
serialNumber, err := rand.Int(rand.Reader, maxBigInt)
if err != nil {
return tls.Certificate{}, err
}
names := []string{cn}
names = append(names, sans...)
template := x509.Certificate{
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
BasicConstraintsValid: true,
NotBefore: time.Now(),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
NotAfter: time.Now().AddDate(0, 1, 0),
SerialNumber: serialNumber,
Version: 2,
IsCA: true,
DNSNames: names,
}
raw, err := x509.CreateCertificate(rand.Reader, &template, &template, pubKey, key)
if err != nil {
return tls.Certificate{}, err
}
return tls.Certificate{
Certificate: [][]byte{raw},
PrivateKey: key,
Leaf: &template,
}, nil
}

View File

@@ -0,0 +1,24 @@
// Package signature provides our implemented Signature Algorithms
package signature
// Algorithm as defined in TLS 1.2
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
type Algorithm uint16
// SignatureAlgorithm enums
const (
Anonymous Algorithm = 0
RSA Algorithm = 1
ECDSA Algorithm = 3
Ed25519 Algorithm = 7
)
// Algorithms returns all implemented Signature Algorithms
func Algorithms() map[Algorithm]struct{} {
return map[Algorithm]struct{}{
Anonymous: {},
RSA: {},
ECDSA: {},
Ed25519: {},
}
}

View File

@@ -0,0 +1,9 @@
package signaturehash
import "errors"
var (
errNoAvailableSignatureSchemes = errors.New("connection can not be created, no SignatureScheme satisfy this Config")
errInvalidSignatureAlgorithm = errors.New("invalid signature algorithm")
errInvalidHashAlgorithm = errors.New("invalid hash algorithm")
)

View File

@@ -0,0 +1,93 @@
// Package signaturehash provides the SignatureHashAlgorithm as defined in TLS 1.2
package signaturehash
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/tls"
"github.com/pion/dtls/v2/pkg/crypto/hash"
"github.com/pion/dtls/v2/pkg/crypto/signature"
"golang.org/x/xerrors"
)
// Algorithm is a signature/hash algorithm pairs which may be used in
// digital signatures.
//
// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
type Algorithm struct {
Hash hash.Algorithm
Signature signature.Algorithm
}
// Algorithms are all the know SignatureHash Algorithms
func Algorithms() []Algorithm {
return []Algorithm{
{hash.SHA256, signature.ECDSA},
{hash.SHA384, signature.ECDSA},
{hash.SHA512, signature.ECDSA},
{hash.SHA256, signature.RSA},
{hash.SHA384, signature.RSA},
{hash.SHA512, signature.RSA},
{hash.Ed25519, signature.Ed25519},
}
}
// SelectSignatureScheme returns most preferred and compatible scheme.
func SelectSignatureScheme(sigs []Algorithm, privateKey crypto.PrivateKey) (Algorithm, error) {
for _, ss := range sigs {
if ss.isCompatible(privateKey) {
return ss, nil
}
}
return Algorithm{}, errNoAvailableSignatureSchemes
}
// isCompatible checks that given private key is compatible with the signature scheme.
func (a *Algorithm) isCompatible(privateKey crypto.PrivateKey) bool {
switch privateKey.(type) {
case ed25519.PrivateKey:
return a.Signature == signature.Ed25519
case *ecdsa.PrivateKey:
return a.Signature == signature.ECDSA
case *rsa.PrivateKey:
return a.Signature == signature.RSA
default:
return false
}
}
// ParseSignatureSchemes translates []tls.SignatureScheme to []signatureHashAlgorithm.
// It returns default signature scheme list if no SignatureScheme is passed.
func ParseSignatureSchemes(sigs []tls.SignatureScheme, insecureHashes bool) ([]Algorithm, error) {
if len(sigs) == 0 {
return Algorithms(), nil
}
out := []Algorithm{}
for _, ss := range sigs {
sig := signature.Algorithm(ss & 0xFF)
if _, ok := signature.Algorithms()[sig]; !ok {
return nil,
xerrors.Errorf("SignatureScheme %04x: %w", ss, errInvalidSignatureAlgorithm)
}
h := hash.Algorithm(ss >> 8)
if _, ok := hash.Algorithms()[h]; !ok || (ok && h == hash.None) {
return nil, xerrors.Errorf("SignatureScheme %04x: %w", ss, errInvalidHashAlgorithm)
}
if h.Insecure() && !insecureHashes {
continue
}
out = append(out, Algorithm{
Hash: h,
Signature: sig,
})
}
if len(out) == 0 {
return nil, errNoAvailableSignatureSchemes
}
return out, nil
}

View File

@@ -0,0 +1,46 @@
// +build go1.13
package signaturehash
import (
"crypto/tls"
"reflect"
"testing"
"github.com/pion/dtls/v2/pkg/crypto/hash"
"github.com/pion/dtls/v2/pkg/crypto/signature"
"golang.org/x/xerrors"
)
func TestParseSignatureSchemes_Ed25519(t *testing.T) {
cases := map[string]struct {
input []tls.SignatureScheme
expected []Algorithm
err error
insecureHashes bool
}{
"Translate": {
input: []tls.SignatureScheme{
tls.Ed25519,
},
expected: []Algorithm{
{hash.Ed25519, signature.Ed25519},
},
err: nil,
insecureHashes: false,
},
}
for name, testCase := range cases {
testCase := testCase
t.Run(name, func(t *testing.T) {
output, err := ParseSignatureSchemes(testCase.input, testCase.insecureHashes)
if testCase.err != nil && !xerrors.Is(err, testCase.err) {
t.Fatalf("Expected error: %v, got: %v", testCase.err, err)
}
if !reflect.DeepEqual(testCase.expected, output) {
t.Errorf("Expected signatureHashAlgorithm:\n%+v\ngot:\n%+v", testCase.expected, output)
}
})
}
}

View File

@@ -0,0 +1,102 @@
package signaturehash
import (
"crypto/tls"
"reflect"
"testing"
"github.com/pion/dtls/v2/pkg/crypto/hash"
"github.com/pion/dtls/v2/pkg/crypto/signature"
"golang.org/x/xerrors"
)
func TestParseSignatureSchemes(t *testing.T) {
cases := map[string]struct {
input []tls.SignatureScheme
expected []Algorithm
err error
insecureHashes bool
}{
"Translate": {
input: []tls.SignatureScheme{
tls.ECDSAWithP256AndSHA256,
tls.ECDSAWithP384AndSHA384,
tls.ECDSAWithP521AndSHA512,
tls.PKCS1WithSHA256,
tls.PKCS1WithSHA384,
tls.PKCS1WithSHA512,
},
expected: []Algorithm{
{hash.SHA256, signature.ECDSA},
{hash.SHA384, signature.ECDSA},
{hash.SHA512, signature.ECDSA},
{hash.SHA256, signature.RSA},
{hash.SHA384, signature.RSA},
{hash.SHA512, signature.RSA},
},
insecureHashes: false,
err: nil,
},
"InvalidSignatureAlgorithm": {
input: []tls.SignatureScheme{
tls.ECDSAWithP256AndSHA256, // Valid
0x04FF, // Invalid: unknown signature with SHA-256
},
expected: nil,
insecureHashes: false,
err: errInvalidSignatureAlgorithm,
},
"InvalidHashAlgorithm": {
input: []tls.SignatureScheme{
tls.ECDSAWithP256AndSHA256, // Valid
0x0003, // Invalid: ECDSA with None
},
expected: nil,
insecureHashes: false,
err: errInvalidHashAlgorithm,
},
"InsecureHashAlgorithmDenied": {
input: []tls.SignatureScheme{
tls.ECDSAWithP256AndSHA256, // Valid
tls.ECDSAWithSHA1, // Insecure
},
expected: []Algorithm{
{hash.SHA256, signature.ECDSA},
},
insecureHashes: false,
err: nil,
},
"InsecureHashAlgorithmAllowed": {
input: []tls.SignatureScheme{
tls.ECDSAWithP256AndSHA256, // Valid
tls.ECDSAWithSHA1, // Insecure
},
expected: []Algorithm{
{hash.SHA256, signature.ECDSA},
{hash.SHA1, signature.ECDSA},
},
insecureHashes: true,
err: nil,
},
"OnlyInsecureHashAlgorithm": {
input: []tls.SignatureScheme{
tls.ECDSAWithSHA1, // Insecure
},
insecureHashes: false,
err: errNoAvailableSignatureSchemes,
},
}
for name, testCase := range cases {
testCase := testCase
t.Run(name, func(t *testing.T) {
output, err := ParseSignatureSchemes(testCase.input, testCase.insecureHashes)
if testCase.err != nil && !xerrors.Is(err, testCase.err) {
t.Fatalf("Expected error: %v, got: %v", testCase.err, err)
}
if !reflect.DeepEqual(testCase.expected, output) {
t.Errorf("Expected signatureHashAlgorithm:\n%+v\ngot:\n%+v", testCase.expected, output)
}
})
}
}