Skip to content

Commit

Permalink
Set public key from private key in DNSKEY instead of copying it from …
Browse files Browse the repository at this point in the history
…DNSKEY to private key

Previously when loading a PrivateKey into a DNSKEY we would return the PrivateKey with the PublicKey set from the DNSKEY struct.
Now that behaviour is flipped and the PublicKey is taken from the PrivateKey and set in the DNSKEY.
It will now fail if the algorithm or PublicKey in the DNSKEY doesn't
match what is loaded in the input file
  • Loading branch information
Martin Frausing committed Apr 28, 2024
1 parent e4ef594 commit b680beb
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 35 deletions.
44 changes: 30 additions & 14 deletions dnssec_keyscan.go
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,53 @@ 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 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
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
38 changes: 38 additions & 0 deletions parse_test.go
Expand Up @@ -1285,6 +1285,44 @@ 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)
}
}

// special input test
Expand Down

0 comments on commit b680beb

Please sign in to comment.