dns/sig0.go

195 lines
4.5 KiB
Go
Raw Permalink Normal View History

package dns
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"encoding/binary"
"math/big"
"strings"
"time"
)
2014-11-02 11:39:12 +00:00
// Sign signs a dns.Msg. It fills the signature with the appropriate data.
// The SIG record should have the SignerName, KeyTag, Algorithm, Inception
// and Expiration set.
func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
if k == nil {
return nil, ErrPrivKey
}
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
return nil, ErrKey
}
rr.Hdr = RR_Header{Name: ".", Rrtype: TypeSIG, Class: ClassANY, Ttl: 0}
rr.OrigTtl, rr.TypeCovered, rr.Labels = 0, 0, 0
Properly calculate compressed message lengths (#833) * Remove fullSize return from compressionLenSearch This wasn't used anywhere but TestCompressionLenSearch, and was very wrong. * Add generated compressedLen functions and use them This replaces the confusing and complicated compressionLenSlice function. * Use compressedLenWithCompressionMap even for uncompressed This leaves the len() functions unused and they'll soon be removed. This also fixes the off-by-one error of compressedLen when a (Q)NAME is ".". * Use Len helper instead of RR.len private method * Merge len and compressedLen functions * Merge compressedLen helper into Msg.Len * Remove compress bool from compressedLenWithCompressionMap * Merge map insertion into compressionLenSearch This eliminates the need to loop over the domain name twice when we're compressing the name. * Use compressedNameLen for NSEC.NextDomain This was a mistake. * Remove compress from RR.len * Add test case for multiple questions length * Add test case for MINFO and SOA compression These are the only RRs with multiple compressible names within the same RR, and they were previously broken. * Rename compressedNameLen to domainNameLen It also handles the length of uncompressed domain names. * Use off directly instead of len(s[:off]) * Move initial maxCompressionOffset check out of compressionLenMapInsert This should allow us to avoid the call overhead of compressionLenMapInsert in certain limited cases and may result in a slight performance increase. compressionLenMapInsert still has a maxCompressionOffset check inside the for loop. * Rename compressedLenWithCompressionMap to msgLenWithCompressionMap This better reflects that it also calculates the uncompressed length. * Merge TestMsgCompressMINFO with TestMsgCompressSOA They're both testing the same thing. * Remove compressionLenMapInsert compressionLenSearch does everything compressionLenMapInsert did anyway. * Only call compressionLenSearch in one place in domainNameLen * Split if statement in domainNameLen The last two commits worsened the performance of domainNameLen noticably, this change restores it's original performance. name old time/op new time/op delta MsgLength-12 550ns ±13% 510ns ±21% ~ (p=0.050 n=10+10) MsgLengthNoCompression-12 26.9ns ± 2% 27.0ns ± 1% ~ (p=0.198 n=9+10) MsgLengthPack-12 2.30µs ±12% 2.26µs ±16% ~ (p=0.739 n=10+10) MsgLengthMassive-12 32.9µs ± 7% 32.0µs ±10% ~ (p=0.243 n=9+10) MsgLengthOnlyQuestion-12 9.60ns ± 1% 9.20ns ± 1% -4.16% (p=0.000 n=9+9) * Remove stray newline from TestMsgCompressionMultipleQuestions * Remove stray newline in length_test.go This was introduced when resolving merge conflicts.
2018-11-29 23:33:41 +00:00
buf := make([]byte, m.Len()+Len(rr))
mbuf, err := m.PackBuffer(buf)
if err != nil {
return nil, err
}
if &buf[0] != &mbuf[0] {
return nil, ErrBuf
}
off, err := PackRR(rr, buf, len(mbuf), nil, false)
if err != nil {
return nil, err
}
buf = buf[:off:cap(buf)]
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if err != nil {
return nil, err
}
// Write SIG rdata
h.Write(buf[len(mbuf)+1+2+2+4+2:])
// Write message
h.Write(buf[:len(mbuf)])
signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
if err != nil {
return nil, err
}
rr.Signature = toBase64(signature)
buf = append(buf, signature...)
if len(buf) > int(^uint16(0)) {
return nil, ErrBuf
}
// Adjust sig data length
rdoff := len(mbuf) + 1 + 2 + 2 + 4
rdlen := binary.BigEndian.Uint16(buf[rdoff:])
rdlen += uint16(len(signature))
binary.BigEndian.PutUint16(buf[rdoff:], rdlen)
// Adjust additional count
adc := binary.BigEndian.Uint16(buf[10:])
adc++
binary.BigEndian.PutUint16(buf[10:], adc)
return buf, nil
}
// Verify validates the message buf using the key k.
// It's assumed that buf is a valid message from which rr was unpacked.
func (rr *SIG) Verify(k *KEY, buf []byte) error {
if k == nil {
return ErrKey
}
if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 {
return ErrKey
}
h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if err != nil {
return err
}
buflen := len(buf)
qdc := binary.BigEndian.Uint16(buf[4:])
anc := binary.BigEndian.Uint16(buf[6:])
auc := binary.BigEndian.Uint16(buf[8:])
adc := binary.BigEndian.Uint16(buf[10:])
offset := headerSize
for i := uint16(0); i < qdc && offset < buflen; i++ {
_, offset, err = UnpackDomainName(buf, offset)
if err != nil {
return err
}
// Skip past Type and Class
offset += 2 + 2
}
for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ {
_, offset, err = UnpackDomainName(buf, offset)
if err != nil {
return err
}
// Skip past Type, Class and TTL
offset += 2 + 2 + 4
if offset+1 >= buflen {
continue
}
Fix dominikh/go-tools nits (#758) * Remove unused functions and consts * Address gosimple nits * Address staticcheck nits This excludes several that were intentional or weren't actual errors. * Reduce size of lex struct This reduces the size of the lex struct by 8 bytes from: lex.token string: 0-16 (size 16, align 8) lex.tokenUpper string: 16-32 (size 16, align 8) lex.length int: 32-40 (size 8, align 8) lex.err bool: 40-41 (size 1, align 1) lex.value uint8: 41-42 (size 1, align 1) padding: 42-48 (size 6, align 0) lex.line int: 48-56 (size 8, align 8) lex.column int: 56-64 (size 8, align 8) lex.torc uint16: 64-66 (size 2, align 2) padding: 66-72 (size 6, align 0) lex.comment string: 72-88 (size 16, align 8) to: lex.token string: 0-16 (size 16, align 8) lex.tokenUpper string: 16-32 (size 16, align 8) lex.length int: 32-40 (size 8, align 8) lex.err bool: 40-41 (size 1, align 1) lex.value uint8: 41-42 (size 1, align 1) lex.torc uint16: 42-44 (size 2, align 2) padding: 44-48 (size 4, align 0) lex.line int: 48-56 (size 8, align 8) lex.column int: 56-64 (size 8, align 8) lex.comment string: 64-80 (size 16, align 8) * Reduce size of response struct This reduces the size of the response struct by 8 bytes from: response.msg []byte: 0-24 (size 24, align 8) response.hijacked bool: 24-25 (size 1, align 1) padding: 25-32 (size 7, align 0) response.tsigStatus error: 32-48 (size 16, align 8) response.tsigTimersOnly bool: 48-49 (size 1, align 1) padding: 49-56 (size 7, align 0) response.tsigRequestMAC string: 56-72 (size 16, align 8) response.tsigSecret map[string]string: 72-80 (size 8, align 8) response.udp *net.UDPConn: 80-88 (size 8, align 8) response.tcp net.Conn: 88-104 (size 16, align 8) response.udpSession *github.com/tmthrgd/dns.SessionUDP: 104-112 (size 8, align 8) response.writer github.com/tmthrgd/dns.Writer: 112-128 (size 16, align 8) response.wg *sync.WaitGroup: 128-136 (size 8, align 8) to: response.msg []byte: 0-24 (size 24, align 8) response.hijacked bool: 24-25 (size 1, align 1) response.tsigTimersOnly bool: 25-26 (size 1, align 1) padding: 26-32 (size 6, align 0) response.tsigStatus error: 32-48 (size 16, align 8) response.tsigRequestMAC string: 48-64 (size 16, align 8) response.tsigSecret map[string]string: 64-72 (size 8, align 8) response.udp *net.UDPConn: 72-80 (size 8, align 8) response.tcp net.Conn: 80-96 (size 16, align 8) response.udpSession *github.com/tmthrgd/dns.SessionUDP: 96-104 (size 8, align 8) response.writer github.com/tmthrgd/dns.Writer: 104-120 (size 16, align 8) response.wg *sync.WaitGroup: 120-128 (size 8, align 8)
2018-09-26 18:32:05 +00:00
rdlen := binary.BigEndian.Uint16(buf[offset:])
offset += 2
offset += int(rdlen)
}
if offset >= buflen {
return &Error{err: "overflowing unpacking signed message"}
}
// offset should be just prior to SIG
bodyend := offset
// owner name SHOULD be root
_, offset, err = UnpackDomainName(buf, offset)
if err != nil {
return err
}
// Skip Type, Class, TTL, RDLen
offset += 2 + 2 + 4 + 2
sigstart := offset
// Skip Type Covered, Algorithm, Labels, Original TTL
offset += 2 + 1 + 1 + 4
if offset+4+4 >= buflen {
return &Error{err: "overflow unpacking signed message"}
}
expire := binary.BigEndian.Uint32(buf[offset:])
offset += 4
incept := binary.BigEndian.Uint32(buf[offset:])
offset += 4
now := uint32(time.Now().Unix())
if now < incept || now > expire {
return ErrTime
}
// Skip key tag
offset += 2
var signername string
signername, offset, err = UnpackDomainName(buf, offset)
if err != nil {
return err
}
// If key has come from the DNS name compression might
// have mangled the case of the name
if !strings.EqualFold(signername, k.Header().Name) {
return &Error{err: "signer name doesn't match key name"}
}
sigend := offset
h.Write(buf[sigstart:sigend])
h.Write(buf[:10])
h.Write([]byte{
byte((adc - 1) << 8),
byte(adc - 1),
})
h.Write(buf[12:bodyend])
hashed := h.Sum(nil)
sig := buf[sigend:]
switch k.Algorithm {
case RSASHA1, RSASHA256, RSASHA512:
pk := k.publicKeyRSA()
if pk != nil {
return rsa.VerifyPKCS1v15(pk, cryptohash, hashed, sig)
}
case ECDSAP256SHA256, ECDSAP384SHA384:
pk := k.publicKeyECDSA()
r := new(big.Int).SetBytes(sig[:len(sig)/2])
s := new(big.Int).SetBytes(sig[len(sig)/2:])
if pk != nil {
if ecdsa.Verify(pk, hashed, r, s) {
return nil
}
return ErrSig
}
case ED25519:
pk := k.publicKeyED25519()
if pk != nil {
if ed25519.Verify(pk, hashed, sig) {
return nil
}
return ErrSig
}
}
return ErrKeyAlg
}