DNSKEY added to zone

This commit is contained in:
Benjamin Fry 2016-05-19 00:00:40 -07:00
parent aab326f9fd
commit 64c1020ba4
8 changed files with 273 additions and 102 deletions

View File

@ -6,6 +6,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Documentation on all modules, and many standard RFC types
- Authority zone signing now complete, still need to load/save private keys
- DNSKEYs auto inserted for added private keys
- New mocked network client tests, to verify zone signing
### Fixed
- Added loop on TCP accept requests

View File

@ -82,6 +82,13 @@ impl Authority {
}
pub fn add_secure_key(&mut self, signer: Signer) {
// also add the key to the zone
let zone_ttl = self.get_soa(false).map_or(0, |soa| if let &RData::SOA(ref soa_rdata) = soa.get_rdata() { soa_rdata.get_minimum() } else { 0 });
let dnskey = signer.to_dnskey(self.origin.clone(), zone_ttl);
// TODO: also generate the CDS and CDNSKEY
let serial = self.get_serial();
self.upsert(dnskey, serial);
self.secure_keys.push(signer);
}
@ -90,6 +97,11 @@ impl Authority {
self.allow_update = allow_update;
}
#[cfg(test)]
pub fn get_secure_keys(&self) -> &[Signer] {
&self.secure_keys
}
pub fn get_origin(&self) -> &Name {
&self.origin
}
@ -726,8 +738,12 @@ impl Authority {
.map_or(0, |soa_rec| if let &RData::SOA(ref soa) = soa_rec.get_rdata() { soa.get_minimum() }
else { panic!("SOA has no RDATA: {}", self.origin) } );
for rr_set in self.records.iter_mut().filter_map(|(_, rr_set)| { rr_set.get_rrsigs().is_empty();
Some(rr_set) } ) {
for rr_set in self.records.iter_mut().filter_map(|(_, rr_set)| {
// do not sign zone DNSKEY's that's the job of the parent zone
if rr_set.get_record_type() == RecordType::DNSKEY { return None }
rr_set.get_rrsigs().is_empty();
Some(rr_set) } ) {
debug!("signing rr_set: {}", rr_set.get_name());
rr_set.clear_rrsigs();
let rrsig_temp = Record::with(rr_set.get_name().clone(), RecordType::RRSIG, zone_ttl);
@ -842,6 +858,21 @@ pub mod authority_tests {
return records;
}
pub fn create_secure_example() -> Authority {
use openssl::crypto::pkey::PKey;
use ::rr::dnssec::{Algorithm, Signer};
let mut authority: Authority = create_example();
let mut pkey = PKey::new();
pkey.gen(512);
let signer = Signer::new(Algorithm::RSASHA256, pkey, authority.get_origin().clone(), u32::max_value(), 0);
authority.add_secure_key(signer);
authority.sign_zone();
authority
}
#[test]
fn test_authority() {
let authority: Authority = create_example();
@ -1050,22 +1081,17 @@ pub mod authority_tests {
#[test]
fn test_zone_signing() {
use openssl::crypto::pkey::PKey;
use ::rr::dnssec::{Algorithm, Signer};
use ::rr::{RData};
let mut authority: Authority = create_example();
let mut pkey = PKey::new();
pkey.gen(512);
let signer = Signer::new(Algorithm::RSASHA256, pkey, Name::root(), u32::max_value(), 0);
authority.add_secure_key(signer);
authority.sign_zone();
let authority: Authority = create_secure_example();
let results = authority.lookup(&authority.get_origin(), RecordType::AXFR, true).expect("AXFR broken");
assert!(results.iter().any(|r| r.get_rr_type() == RecordType::DNSKEY), "must contain a DNSKEY");
for record in results.iter() {
if record.get_rr_type() == RecordType::RRSIG { continue }
if record.get_rr_type() == RecordType::DNSKEY { continue }
// validate all records have associated RRSIGs after signing
assert!(results.iter().any(|r| r.get_rr_type() == RecordType::RRSIG &&

View File

@ -217,14 +217,15 @@ impl Catalog {
if let Some(ref_authority) = self.find_auth_recurse(query.get_name()) {
let authority = &ref_authority.read().unwrap(); // poison errors should panic
debug!("found authority: {:?}", authority.get_origin());
let is_dnssec = request.get_edns().map_or(false, |edns|edns.is_dnssec_ok());
if let Some(records) = Self::search(&*authority, query) {
if let Some(records) = Self::search(&*authority, query, is_dnssec) {
response.response_code(ResponseCode::NoError);
response.authoritative(true);
response.add_all_answers(&records);
// get the NS records
let ns = authority.get_ns(false);
let ns = authority.get_ns(is_dnssec);
if ns.is_none() { warn!("there are no NS records for: {:?}", authority.get_origin()); }
else {
response.add_all_name_servers(&ns.unwrap());
@ -233,7 +234,7 @@ impl Catalog {
// in the not found case it's standard to return the SOA in the authority section
response.response_code(ResponseCode::NXDomain);
let soa = authority.get_soa(false);
let soa = authority.get_soa(is_dnssec);
if soa.is_none() { warn!("there is no SOA record for: {:?}", authority.get_origin()); }
else {
response.add_name_server(soa.unwrap().clone());
@ -251,7 +252,7 @@ impl Catalog {
}
// TODO: move this to Authority
pub fn search<'a>(authority: &'a Authority, query: &Query) -> Option<Vec<&'a Record>> {
pub fn search<'a>(authority: &'a Authority, query: &Query, is_secure: bool) -> Option<Vec<&'a Record>> {
let record_type: RecordType = query.get_query_type();
// if this is an AXFR zone transfer, verify that this is either the slave or master
@ -266,7 +267,7 @@ impl Catalog {
// it would be better to stream this back, rather than packaging everything up in an array
// though for UDP it would still need to be bundled
let mut query_result: Option<Vec<_>> = authority.lookup(query.get_name(), record_type, false);
let mut query_result: Option<Vec<_>> = authority.lookup(query.get_name(), record_type, is_secure);
if RecordType::AXFR == record_type {
if let Some(soa) = authority.get_soa(false) {
@ -300,14 +301,16 @@ impl Catalog {
#[cfg(test)]
mod catalog_tests {
use std::net::*;
use std::collections::*;
use ::authority::{Authority, ZoneType};
use ::authority::authority_tests::create_example;
use super::*;
use ::op::*;
use ::rr::*;
use ::rr::rdata::SOA;
use ::op::*;
use std::net::*;
use super::*;
#[test]
fn test_catalog_search() {
@ -317,7 +320,7 @@ mod catalog_tests {
let mut query: Query = Query::new();
query.name(origin.clone());
if let Some(result) = Catalog::search(&example, &query) {
if let Some(result) = Catalog::search(&example, &query, false) {
assert_eq!(result.first().unwrap().get_rr_type(), RecordType::A);
assert_eq!(result.first().unwrap().get_dns_class(), DNSClass::IN);
assert_eq!(result.first().unwrap().get_rdata(), &RData::A(Ipv4Addr::new(93,184,216,34)));
@ -335,7 +338,7 @@ mod catalog_tests {
let mut query: Query = Query::new();
query.name(www_name.clone());
if let Some(result) = Catalog::search(&example, &query) {
if let Some(result) = Catalog::search(&example, &query, false) {
assert_eq!(result.first().unwrap().get_rr_type(), RecordType::A);
assert_eq!(result.first().unwrap().get_dns_class(), DNSClass::IN);
assert_eq!(result.first().unwrap().get_rdata(), &RData::A(Ipv4Addr::new(93,184,216,34)));

View File

@ -33,12 +33,21 @@ use ::client::ClientConnection;
pub struct Client<C: ClientConnection> {
client_connection: RefCell<C>,
next_id: Cell<u16>,
trust_anchor: TrustAnchor,
}
impl<C: ClientConnection> Client<C> {
/// name_server to connect to with default port 53
pub fn new(client_connection: C) -> Client<C> {
Client{ client_connection: RefCell::new(client_connection), next_id: Cell::new(1037) }
Client{ client_connection: RefCell::new(client_connection),
next_id: Cell::new(1037),
trust_anchor: TrustAnchor::default() }
}
pub fn with_trust_anchor(client_connection: C, trust_anchor: TrustAnchor) -> Client<C> {
Client{ client_connection: RefCell::new(client_connection),
next_id: Cell::new(1037),
trust_anchor: trust_anchor }
}
/// When the resolver receives an answer via the normal DNS lookup process, it then checks to
@ -86,7 +95,7 @@ impl<C: ClientConnection> Client<C> {
// 'com. DS' is signed by '. DNSKEY' which produces 'com. RRSIG', all are in the same zone, '.'
// the '.' DNSKEY is signed by the well known root certificate.
// TODO fix rrsigs clone()
let proof = try!(self.recursive_query_verify(&name, rrset, rrsigs.clone(), query_type, query_class));
let proof = try!(self.recursive_query_verify(&name, rrset, rrsigs.clone(), rrset_type, query_class));
// TODO return this, also make a prettier print
debug!("proved existance through: {:?}", proof);
@ -198,11 +207,9 @@ impl<C: ClientConnection> Client<C> {
fn verify_dnskey(&self, dnskey: &Record) -> ClientResult<Vec<Record>> {
let name: &domain::Name = dnskey.get_name();
if dnskey.get_name().is_root() {
if let &RData::DNSKEY(ref rdata) = dnskey.get_rdata() {
if TrustAnchor::new().contains(rdata.get_public_key()) {
return Ok(vec![dnskey.clone()])
}
if let &RData::DNSKEY(ref rdata) = dnskey.get_rdata() {
if self.trust_anchor.contains(rdata.get_public_key()) {
return Ok(vec![dnskey.clone()])
}
}
@ -302,7 +309,7 @@ impl<C: ClientConnection> Client<C> {
// corresponding RRSIG RR, a validator MUST ignore the settings of the
// NSEC and RRSIG bits in an NSEC RR.
fn verify_nsec(&self, query_name: &domain::Name, query_type: RecordType,
query_class: DNSClass, nsecs: Vec<&Record>) -> ClientResult<()> {
_: DNSClass, nsecs: Vec<&Record>) -> ClientResult<()> {
debug!("verifying nsec");
// first look for a record with the same name
@ -403,7 +410,7 @@ impl<C: ClientConnection> Client<C> {
// equivalent algorithm) proves that X does not exist by proving that an
// ancestor of X is its closest encloser.
fn verify_nsec3(&self, query_name: &domain::Name, query_type: RecordType,
query_class: DNSClass, soa: Option<&Record>,
_: DNSClass, soa: Option<&Record>,
nsec3s: Vec<&Record>) -> ClientResult<()> {
// the search name is the one to look for
let zone_name = try!(soa.ok_or(ClientError::NoSOARecord(query_name.clone()))).get_name();
@ -529,23 +536,40 @@ impl<C: ClientConnection> Client<C> {
}
#[cfg(test)]
#[cfg(feature = "ftest")]
mod test {
use std::net::*;
use ::rr::{DNSClass, RecordType, domain, RData};
use ::authority::Catalog;
use ::authority::authority_tests::{create_example, create_secure_example};
use ::client::{Client, ClientConnection, TestClientConnection};
#[cfg(feature = "ftest")]
use ::op::ResponseCode;
use ::udp::UdpClientConnection;
use ::rr::{DNSClass, RecordType, domain, RData};
use ::rr::dnssec::TrustAnchor;
#[cfg(feature = "ftest")]
use ::tcp::TcpClientConnection;
use super::Client;
use super::super::ClientConnection;
#[cfg(feature = "ftest")]
use ::udp::UdpClientConnection;
#[test]
fn test_query_nonet() {
let authority = create_example();
let mut catalog = Catalog::new();
catalog.upsert(authority.get_origin().clone(), authority);
let client = Client::new(TestClientConnection::new(&catalog));
test_query(client);
}
#[test]
#[cfg(feature = "ftest")]
fn test_query_udp() {
let addr: SocketAddr = ("8.8.8.8",53).to_socket_addrs().unwrap().next().unwrap();
let conn = UdpClientConnection::new(addr).unwrap();
test_query(conn);
let client = Client::new(conn);
test_query(client);
}
#[test]
@ -553,16 +577,14 @@ mod test {
fn test_query_tcp() {
let addr: SocketAddr = ("8.8.8.8",53).to_socket_addrs().unwrap().next().unwrap();
let conn = TcpClientConnection::new(addr).unwrap();
test_query(conn);
let client = Client::new(conn);
test_query(client);
}
// TODO: this should be flagged with cfg as a functional test.
#[cfg(test)]
#[cfg(feature = "ftest")]
fn test_query<C: ClientConnection>(conn: C) {
fn test_query<C: ClientConnection>(client: Client<C>) {
let name = domain::Name::with_labels(vec!["www".to_string(), "example".to_string(), "com".to_string()]);
let client = Client::new(conn);
let response = client.query(&name, DNSClass::IN, RecordType::A);
assert!(response.is_ok(), "query failed: {}", response.unwrap_err());
@ -583,12 +605,36 @@ mod test {
}
}
#[test]
fn test_secure_query_example_nonet() {
use ::client::client_connection::test::TestClientConnection;
let authority = create_secure_example();
let public_key = {
let signers = authority.get_secure_keys();
signers.first().expect("expected a key in the authority").get_public_key()
};
let mut catalog = Catalog::new();
catalog.upsert(authority.get_origin().clone(), authority);
let mut trust_anchor = TrustAnchor::new();
trust_anchor.insert_trust_anchor(public_key);
let client = Client::with_trust_anchor(TestClientConnection::new(&catalog), trust_anchor);
test_secure_query_example(client);
}
#[test]
#[cfg(feature = "ftest")]
fn test_secure_query_example_udp() {
let addr: SocketAddr = ("8.8.8.8",53).to_socket_addrs().unwrap().next().unwrap();
let conn = UdpClientConnection::new(addr).unwrap();
test_secure_query_example(conn);
let client = Client::new(conn);
test_secure_query_example(client);
}
#[test]
@ -596,21 +642,22 @@ mod test {
fn test_secure_query_example_tcp() {
let addr: SocketAddr = ("8.8.8.8",53).to_socket_addrs().unwrap().next().unwrap();
let conn = TcpClientConnection::new(addr).unwrap();
test_secure_query_example(conn);
let client = Client::new(conn);
test_secure_query_example(client);
}
#[cfg(test)]
#[cfg(feature = "ftest")]
fn test_secure_query_example<C: ClientConnection>(conn: C) {
fn test_secure_query_example<C: ClientConnection>(client: Client<C>) {
let name = domain::Name::with_labels(vec!["www".to_string(), "example".to_string(), "com".to_string()]);
let client = Client::new(conn);
let response = client.secure_query(&name, DNSClass::IN, RecordType::A);
assert!(response.is_ok(), "query failed: {}", response.unwrap_err());
assert!(response.is_ok(), "query for {} failed: {}", name, response.unwrap_err());
let response = response.unwrap();
println!("response records: {:?}", response);
assert!(response.get_edns().expect("edns not here").is_dnssec_ok());
let record = &response.get_answers()[0];
assert_eq!(record.get_name(), &name);

View File

@ -20,3 +20,46 @@ pub trait ClientConnection: Sized+Debug {
fn send(&mut self, bytes: Vec<u8>) -> ClientResult<Vec<u8>>;
// TODO: split send and read...
}
#[cfg(test)]
pub mod test {
use std::fmt;
use super::*;
use ::op::Message;
use ::authority::Catalog;
use ::serialize::binary::{BinDecoder, BinEncoder, BinSerializable};
use ::error::*;
pub struct TestClientConnection<'a> {
catalog: &'a Catalog
}
impl<'a> TestClientConnection<'a> {
pub fn new(catalog: &'a Catalog) -> TestClientConnection<'a> {
TestClientConnection { catalog: catalog }
}
}
impl<'a> ClientConnection for TestClientConnection<'a> {
fn send(&mut self, bytes: Vec<u8>) -> ClientResult<Vec<u8>> {
let mut decoder = BinDecoder::new(&bytes);
let message = try!(Message::read(&mut decoder));
let response = self.catalog.handle_request(&message);
let mut buf = Vec::with_capacity(512);
{
let mut encoder = BinEncoder::new(&mut buf);
try!(response.emit(&mut encoder));
}
Ok(buf)
}
}
impl<'a> fmt::Debug for TestClientConnection<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TestClientConnection catalog")
}
}
}

View File

@ -22,3 +22,5 @@ mod client_connection;
pub use self::client::Client;
pub use self::client_connection::ClientConnection;
#[cfg(test)]
pub use self::client_connection::test::TestClientConnection;

View File

@ -13,14 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! signer is a structure for performing many of the signing processes of the DNSSec specification
use openssl::crypto::pkey::{PKey, Role};
use ::op::Message;
use ::rr::dnssec::{Algorithm, DigestType};
use ::rr::{DNSClass, Name, Record, RecordType, RData};
use ::serialize::binary::{BinEncoder, BinSerializable};
use ::rr::rdata::sig;
use ::rr::rdata::{sig, DNSKEY};
/// Use for performing signing and validation of DNSSec based components.
pub struct Signer {
algorithm: Algorithm,
pkey: PKey,
@ -30,10 +35,12 @@ pub struct Signer {
}
impl Signer {
/// Version of Signer for verifying RRSIGs and SIG0 records.
pub fn new_verifier(algorithm: Algorithm, pkey: PKey, signer_name: Name) -> Self {
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, expiration: 0, inception: 0 }
}
/// Version of Signer for signing RRSIGs and SIG0 records.
pub fn new(algorithm: Algorithm, pkey: PKey, signer_name: Name, expiration: u32, inception: u32) -> Self {
Signer{ algorithm: algorithm, pkey: pkey, signer_name: signer_name, expiration: expiration, inception: inception }
}
@ -42,54 +49,77 @@ impl Signer {
pub fn get_signer_name(&self) -> &Name { &self.signer_name }
pub fn get_expiration(&self) -> u32 { self.expiration }
pub fn get_inception(&self) -> u32 { self.inception }
pub fn get_pkey(&self) -> &PKey { &self.pkey }
// RFC 2535 DNS Security Extensions March 1999
//
// 4.1.6 Key Tag Field
//
// The "key Tag" is a two octet quantity that is used to efficiently
// select between multiple keys which may be applicable and thus check
// that a public key about to be used for the computationally expensive
// effort to check the signature is possibly valid. For algorithm 1
// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two
// octets of the public key modulus needed to decode the signature
// field. That is to say, the most significant 16 of the least
// significant 24 bits of the modulus in network (big endian) order. For
// all other algorithms, including private algorithms, it is calculated
// as a simple checksum of the KEY RR as described in Appendix C.
//
// Appendix C: Key Tag Calculation
//
// The key tag field in the SIG RR is just a means of more efficiently
// selecting the correct KEY RR to use when there is more than one KEY
// RR candidate available, for example, in verifying a signature. It is
// possible for more than one candidate key to have the same tag, in
// which case each must be tried until one works or all fail. The
// following reference implementation of how to calculate the Key Tag,
// for all algorithms other than algorithm 1, is in ANSI C. It is coded
// for clarity, not efficiency. (See section 4.1.6 for how to determine
// the Key Tag of an algorithm 1 key.)
//
// /* assumes int is at least 16 bits
// first byte of the key tag is the most significant byte of return
// value
// second byte of the key tag is the least significant byte of
// return value
// */
//
// int keytag (
//
// unsigned char key[], /* the RDATA part of the KEY RR */
// unsigned int keysize, /* the RDLENGTH */
// )
// {
// long int ac; /* assumed to be 32 bits or larger */
//
// for ( ac = 0, i = 0; i < keysize; ++i )
// ac += (i&1) ? key[i] : key[i]<<8;
// ac += (ac>>16) & 0xFFFF;
// return ac & 0xFFFF;
// }
pub fn get_public_key(&self) -> Vec<u8> {
self.algorithm.public_key_to_vec(&self.pkey)
}
/// Creates a Record that represents the public key for this Signer
pub fn to_dnskey(&self, name: Name, ttl: u32) -> Record {
let mut record = Record::with(name.clone(), RecordType::DNSKEY, ttl);
record.rdata(RData::DNSKEY(
DNSKEY::new(true, true, false,
self.algorithm,
self.get_public_key())
));
record
}
/// The key tag is calculated as a hash to more quickly lookup a DNSKEY.
///
/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035)
///
/// ```text
/// RFC 2535 DNS Security Extensions March 1999
///
/// 4.1.6 Key Tag Field
///
/// The "key Tag" is a two octet quantity that is used to efficiently
/// select between multiple keys which may be applicable and thus check
/// that a public key about to be used for the computationally expensive
/// effort to check the signature is possibly valid. For algorithm 1
/// (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two
/// octets of the public key modulus needed to decode the signature
/// field. That is to say, the most significant 16 of the least
/// significant 24 bits of the modulus in network (big endian) order. For
/// all other algorithms, including private algorithms, it is calculated
/// as a simple checksum of the KEY RR as described in Appendix C.
///
/// Appendix C: Key Tag Calculation
///
/// The key tag field in the SIG RR is just a means of more efficiently
/// selecting the correct KEY RR to use when there is more than one KEY
/// RR candidate available, for example, in verifying a signature. It is
/// possible for more than one candidate key to have the same tag, in
/// which case each must be tried until one works or all fail. The
/// following reference implementation of how to calculate the Key Tag,
/// for all algorithms other than algorithm 1, is in ANSI C. It is coded
/// for clarity, not efficiency. (See section 4.1.6 for how to determine
/// the Key Tag of an algorithm 1 key.)
///
/// /* assumes int is at least 16 bits
/// first byte of the key tag is the most significant byte of return
/// value
/// second byte of the key tag is the least significant byte of
/// return value
/// */
///
/// int keytag (
///
/// unsigned char key[], /* the RDATA part of the KEY RR */
/// unsigned int keysize, /* the RDLENGTH */
/// )
/// {
/// long int ac; /* assumed to be 32 bits or larger */
///
/// for ( ac = 0, i = 0; i < keysize; ++i )
/// ac += (i&1) ? key[i] : key[i]<<8;
/// ac += (ac>>16) & 0xFFFF;
/// return ac & 0xFFFF;
/// }
/// ```
pub fn calculate_key_tag(&self) -> u16 {
let mut ac: usize = 0;
@ -119,6 +149,7 @@ impl Signer {
DigestType::from(self.algorithm).hash(&buf)
}
/// ```text
/// 4.1.8.1 Calculating Transaction and Request SIGs
///
/// A response message from a security aware server may optionally
@ -155,6 +186,7 @@ impl Signer {
///
/// Except where needed to authenticate an update or similar privileged
/// request, servers are not required to check request SIGs.
/// ```
/// ---
///
/// NOTE: In classic RFC style, this is unclear, it implies that each SIG record is not included in

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
use std::io::Cursor;
use std::default::Default;
use openssl::crypto::pkey::{PKey, Role};
@ -21,12 +22,14 @@ use ::rr::dnssec::Algorithm;
const ROOT_ANCHOR: &'static str = include_str!("Kjqmt7v.pem");
// TODO: these should also store some information, or more specifically, metadata from the signed
// public certificate.
pub struct TrustAnchor {
pkey: Vec<u8>
pkeys: Vec<Vec<u8>>
}
impl TrustAnchor {
pub fn new() -> TrustAnchor {
impl Default for TrustAnchor {
fn default() -> TrustAnchor {
let mut cursor = Cursor::new(ROOT_ANCHOR);
let pkey = PKey::public_key_from_pem(&mut cursor).expect("Error parsing Kjqmt7v.pem");
assert!(pkey.can(Role::Verify));
@ -34,10 +37,23 @@ impl TrustAnchor {
let alg = Algorithm::RSASHA256;
TrustAnchor{ pkey: alg.public_key_to_vec(&pkey) }
TrustAnchor{ pkeys: vec![alg.public_key_to_vec(&pkey)] }
}
}
impl TrustAnchor {
pub fn new() -> TrustAnchor {
TrustAnchor { pkeys: vec![] }
}
pub fn contains(&self, other_key: &[u8]) -> bool {
self.pkey == other_key
self.pkeys.iter().any(|k|other_key == k as &[u8])
}
/// inserts the trust_anchor to the trusted chain
pub fn insert_trust_anchor(&mut self, public_key: Vec<u8>) {
if !self.contains(&public_key) {
self.pkeys.push(public_key)
}
}
}