Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set public key from private key in DNSKEY instead of copying it from DNSKEY to private key #1560

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 34 additions & 14 deletions dnssec_keyscan.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"io"
"math/big"
Expand All @@ -14,6 +15,7 @@ import (

// NewPrivateKey returns a PrivateKey by parsing the string s.
// s should be in the same form of the BIND private key files.
// Will set the PublicKey in the DNSKEY to the PublicKey contained within the PrivateKey
func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) {
if s == "" || s[len(s)-1] != '\n' { // We need a closing newline
return k.ReadPrivateKey(strings.NewReader(s+"\n"), "")
Expand All @@ -23,8 +25,6 @@ func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) {

// ReadPrivateKey reads a private key from the io.Reader q. The string file is
// only used in error reporting.
// The public key must be known, because some cryptographic algorithms embed
// the public inside the privatekey.
func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) {
m, err := parseKey(q, file)
if m == nil {
Expand All @@ -36,37 +36,57 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" {
return nil, ErrPrivKey
}
// TODO(mg): check if the pubkey matches the private key
algoStr, _, _ := strings.Cut(m["algorithm"], " ")
algo, err := strconv.ParseUint(algoStr, 10, 8)
_algo, err := strconv.ParseUint(algoStr, 10, 8)
algo := uint8(_algo)
if err != nil {
return nil, ErrPrivKey
}
switch uint8(algo) {

// if algorithm isn't set in dnskey, set it from loaded file
if k.Algorithm == 0 {
k.Algorithm = algo
} else if algo != k.Algorithm {
return nil, ErrKeyAlgMismatch
}
prevPublicKey := k.PublicKey
switch algo {
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
priv, err := readPrivateKeyRSA(m)
if err != nil {
return nil, err
}
pub := k.publicKeyRSA()
if pub == nil {
return nil, ErrKey
k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N)
if prevPublicKey != "" && prevPublicKey != k.PublicKey {
return nil, ErrPubKeyMismatch
}
priv.PublicKey = *pub
return priv, nil
case ECDSAP256SHA256, ECDSAP384SHA384:
priv, err := readPrivateKeyECDSA(m)
if err != nil {
return nil, err
}
pub := k.publicKeyECDSA()
if pub == nil {
return nil, ErrKey
switch algo {
case ECDSAP256SHA256:
priv.PublicKey.Curve = elliptic.P256()
case ECDSAP384SHA384:
priv.PublicKey.Curve = elliptic.P384()
}
priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(priv.D.Bytes())
k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y)
if prevPublicKey != "" && prevPublicKey != k.PublicKey {
return nil, ErrPubKeyMismatch
}
priv.PublicKey = *pub
return priv, nil
case ED25519:
return readPrivateKeyED25519(m)
priv, err := readPrivateKeyED25519(m)
if err != nil {
return nil, err
}
if prevPublicKey != "" && prevPublicKey != k.PublicKey {
return nil, ErrPubKeyMismatch
}
return priv, nil
default:
return nil, ErrAlg
}
Expand Down
44 changes: 23 additions & 21 deletions msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,29 @@ const (

// Errors defined in this package.
var (
ErrAlg error = &Error{err: "bad algorithm"} // ErrAlg indicates an error with the (DNSSEC) algorithm.
ErrAuth error = &Error{err: "bad authentication"} // ErrAuth indicates an error in the TSIG authentication.
ErrBuf error = &Error{err: "buffer size too small"} // ErrBuf indicates that the buffer used is too small for the message.
ErrConnEmpty error = &Error{err: "conn has no connection"} // ErrConnEmpty indicates a connection is being used before it is initialized.
ErrExtendedRcode error = &Error{err: "bad extended rcode"} // ErrExtendedRcode ...
ErrFqdn error = &Error{err: "domain must be fully qualified"} // ErrFqdn indicates that a domain name does not have a closing dot.
ErrId error = &Error{err: "id mismatch"} // ErrId indicates there is a mismatch with the message's ID.
ErrKeyAlg error = &Error{err: "bad key algorithm"} // ErrKeyAlg indicates that the algorithm in the key is not valid.
ErrKey error = &Error{err: "bad key"}
ErrKeySize error = &Error{err: "bad key size"}
ErrLongDomain error = &Error{err: fmt.Sprintf("domain name exceeded %d wire-format octets", maxDomainNameWireOctets)}
ErrNoSig error = &Error{err: "no signature found"}
ErrPrivKey error = &Error{err: "bad private key"}
ErrRcode error = &Error{err: "bad rcode"}
ErrRdata error = &Error{err: "bad rdata"}
ErrRRset error = &Error{err: "bad rrset"}
ErrSecret error = &Error{err: "no secrets defined"}
ErrShortRead error = &Error{err: "short read"}
ErrSig error = &Error{err: "bad signature"} // ErrSig indicates that a signature can not be cryptographically validated.
ErrSoa error = &Error{err: "no SOA"} // ErrSOA indicates that no SOA RR was seen when doing zone transfers.
ErrTime error = &Error{err: "bad time"} // ErrTime indicates a timing error in TSIG authentication.
ErrAlg error = &Error{err: "bad algorithm"} // ErrAlg indicates an error with the (DNSSEC) algorithm.
ErrAuth error = &Error{err: "bad authentication"} // ErrAuth indicates an error in the TSIG authentication.
ErrBuf error = &Error{err: "buffer size too small"} // ErrBuf indicates that the buffer used is too small for the message.
ErrConnEmpty error = &Error{err: "conn has no connection"} // ErrConnEmpty indicates a connection is being used before it is initialized.
ErrExtendedRcode error = &Error{err: "bad extended rcode"} // ErrExtendedRcode ...
ErrFqdn error = &Error{err: "domain must be fully qualified"} // ErrFqdn indicates that a domain name does not have a closing dot.
ErrId error = &Error{err: "id mismatch"} // ErrId indicates there is a mismatch with the message's ID.
ErrKeyAlg error = &Error{err: "bad key algorithm"} // ErrKeyAlg indicates that the algorithm in the key is not valid.
ErrKeyAlgMismatch error = &Error{err: "algorithm in file does not match key"} // ErrKeyAlgMismatch indicates that the algorithm in the key doesn't match the loaded algorithm from file
ErrPubKeyMismatch error = &Error{err: "public key in file does not match public key"} // ErrPubKeyMismatch indicates that the public key in the key doesn't match the public key from the private key loaded from file
ErrKey error = &Error{err: "bad key"}
ErrKeySize error = &Error{err: "bad key size"}
ErrLongDomain error = &Error{err: fmt.Sprintf("domain name exceeded %d wire-format octets", maxDomainNameWireOctets)}
ErrNoSig error = &Error{err: "no signature found"}
ErrPrivKey error = &Error{err: "bad private key"}
ErrRcode error = &Error{err: "bad rcode"}
ErrRdata error = &Error{err: "bad rdata"}
ErrRRset error = &Error{err: "bad rrset"}
ErrSecret error = &Error{err: "no secrets defined"}
ErrShortRead error = &Error{err: "short read"}
ErrSig error = &Error{err: "bad signature"} // ErrSig indicates that a signature can not be cryptographically validated.
ErrSoa error = &Error{err: "no SOA"} // ErrSOA indicates that no SOA RR was seen when doing zone transfers.
ErrTime error = &Error{err: "bad time"} // ErrTime indicates a timing error in TSIG authentication.
)

// Id by default returns a 16-bit random number to be used as a message id. The
Expand Down
56 changes: 56 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,62 @@ func TestNewPrivateKey(t *testing.T) {
t.Errorf("[%v] Private keys differ:\n%#v\n%#v", AlgorithmToString[algo.name], privkey, newPrivKey)
}
}

rsaKey := new(DNSKEY)
rsaKey.Hdr.Rrtype = TypeDNSKEY
rsaKey.Hdr.Name = "miek.nl."
rsaKey.Hdr.Class = ClassINET
rsaKey.Hdr.Ttl = 14400
rsaKey.Flags = 256
rsaKey.Protocol = 3
rsaKey.Algorithm = RSASHA256
_, err := rsaKey.Generate(512)
if err != nil {
t.Fatal(err)
}
ecdsapKey := new(DNSKEY)
ecdsapKey.Hdr.Rrtype = TypeDNSKEY
ecdsapKey.Hdr.Name = "miek.nl."
ecdsapKey.Hdr.Class = ClassINET
ecdsapKey.Hdr.Ttl = 14400
ecdsapKey.Flags = 256
ecdsapKey.Protocol = 3
ecdsapKey.Algorithm = ECDSAP256SHA256
ecdsapPrivkey, err := ecdsapKey.Generate(256)
if err != nil {
t.Fatal(err)
}

// test that algo in input matches algo in DNSKEY
_, err = rsaKey.NewPrivateKey(ecdsapKey.PrivateKeyString(ecdsapPrivkey))
if err != ErrKeyAlgMismatch {
t.Fatalf("unexpected err: %v", err)
}

// test that public key in input matches public key in DNSKEY
ecdsapKey.PublicKey = rsaKey.PublicKey
_, err = ecdsapKey.NewPrivateKey(ecdsapKey.PrivateKeyString(ecdsapPrivkey))
if err != ErrPubKeyMismatch {
t.Fatalf("unexpected err: %v", err)
}
// load using an empty dnskey
ecdsapNewKey := new(DNSKEY)
ecdsapNewKey.Hdr.Rrtype = TypeDNSKEY
ecdsapNewKey.Hdr.Name = "miek.nl."
ecdsapNewKey.Hdr.Class = ClassINET
ecdsapNewKey.Hdr.Ttl = 14400
ecdsapNewKey.Flags = 256
ecdsapNewKey.Protocol = 3
_, err = ecdsapNewKey.NewPrivateKey(ecdsapKey.PrivateKeyString(ecdsapPrivkey))
if err != nil {
t.Fatal(err)
}
if ecdsapNewKey.PublicKey != ecdsapKey.PublicKey {
t.Fatalf("PublicKey of new dnskey %s should match key in key loaded: %s", ecdsapNewKey.PublicKey, ecdsapKey.PublicKey)
}
if ecdsapNewKey.Algorithm != ECDSAP256SHA256 {
t.Fatalf("Algorithm of new dnskey %d is set to algorithm in key loaded", ecdsapNewKey.Algorithm)
}
}

// special input test
Expand Down