Security
This page is under construction.
Security is baked into the core of NDN, and is a fundamental part of the architecture. NDN utilizes a data-centric name-based security model, where every piece of data is signed by its producer. This allows NDN to provide data-centric security, privacy, and trustworthiness. The following sections provide an overview of the security features of NDN.
Signature
In NDN, every Data packet is digitally signed by its producer. The signature guarantees the integrity of the packet, because the signature can only be generated by the original producer, and becomes invalid if the encoded packet is modified.
Digital signing is done by a pair of asymmetric keys, which consists of a private key and a public key. The private key is known only by the producer, used to generate signatures. Anyone who knows the public key can verify the signature.
import ndn.encoding as enc
import ndn.security as sec
from Cryptodome.PublicKey import ECC
# Generate key pairs (Recommend 'P-256' for ECDSA and 'ed25519' for EdDSA)
priv_key = ECC.generate(curve='ed25519')
pub_key = priv_key.public_key()
# Create a signer
signer = sec.Ed25519Signer('/edu/ucla/xinyu.ma', priv_key.export_key(format='DER'))
# Sign a data with it
data_wire = enc.make_data(
# String can be directly used as Name in most cases
name='/edu/ucla/cs/118/notes',
# Set the Interest packet's FreshnessPeriod to 10 seconds
meta_info=enc.MetaInfo(freshness_period=10000),
# Set the Data packet's content to "Hello, NDN!"
content=b'Hello, NDN!',
signer=signer
)
print('Data:', data_wire.hex())
# Export public keys
pub_key_bits = pub_key.export_key(format='DER')
print('Public Key bits:', pub_key_bits.hex())
# Can be imported by: ECC.import_key(pub_key_bits)
# Then verify the Data packet using it
_, _, _, sig_ptrs = enc.parse_data(data_wire)
if sec.verify_ed25519(pub_key, sig_ptrs):
print('Data verified')
else:
print('Data not verified')
import { Data, Name } from '@ndn/packet';
import { Decoder, Encoder } from '@ndn/tlv';
import { toHex, toUtf8 } from '@ndn/util';
import { Ed25519, generateSigningKey } from '@ndn/keychain';
// Generate key pairs (Recommend ECDSA and Ed25519 for EdDSA)
const identityName = new Name('/edu/ucla/xinyu.ma');
const [signer, verifier] = await generateSigningKey(identityName, Ed25519);
// Sign a Data with it
const data = new Data(
new Name('/edu/ucla/cs/118/notes'),
Data.FreshnessPeriod(10000),
toUtf8('Hello, NDN!'));
await signer.sign(data);
// Print the Data wire
const wire = Encoder.encode(data);
console.log('Data:', toHex(wire));
// Export public keys
const publicKeyBits = verifier.spki!;
console.log('Public Key bits:', toHex(publicKeyBits));
// Importing a public key in NDNts is very complicated
// so I recommend to use a certificate instead.
// I will show you how to do it later.
// Then verify the Data packet using it
const decodedData = Decoder.decode(wire, Data); // Be the same as `data`
try {
await verifier.verify(decodedData);
console.log('Data verified');
} catch {
console.log('Data not verified');
}
In a system, security is more than cryptographically verifying the signature. For example, we also need to
- Securely obtain the public key of the producer.
- A signed piece of data containing the pubic key is called a certificate.
- Associate the producer with a member (human or process) in the system.
- Make sure the member is allowed to sign the data.
These tasks can be roughly classified into four aspects:
- Bootstrapping: to enroll a new member into an application. Make sure that the new member learns necessary security information to recognize others, and the other members are able to securely recognize new member.
- Management: to manage the membership and certificates, including certification revocation, renewal, and other operations.
- Authentication: to verify a piece of data is produced by claimed producer and not manipulated by an impersonator. This includes cryptographically verifying the signature.
- Authorization: a piece of data is produced by an intended producer, and accessed by an intended consumer. This includes discarding of data whose producers are not allowed to produce, and encrypting data so that only permitted consumers can decrypt.
The classification described is not strict. Most work on NDN involves more than one aspects of the security.
Trust Domain
A trust domain is a collection of named entities under the same administrative control. For example, if a research group uses some NDN software to write papers, then the papers and users' certificates involved in that software makes a trust domain.
A trust zone is usually configured with a trust anchor, a self-signed certificate whose name prefix reflects the name of the trust domain. The trust anchor is called an "anchor" because all trust relations in the domain starts from it. Suppose in the above paper writing example, the professor uses an admin certificate to sign student's public key, and students uses their own key to sign changes they make to the paper. Then, we have the following chain of signing:
flowchart LR
Admin -->|signs| Student -->|signs| paper[Paper Changes]
All signatures start from the admin's certificate if we transitively follow the chain of signing. The entity (member or process) that owns the trust anchor has the power to control all trust relations within the trust domain. Entities managing the trust relationship of the trust domain (including memberships and roles) are called controllers. The owner of the trust anchor is always a controller.
from datetime import datetime, timedelta, timezone
import ndn.encoding as enc
import ndn.security as sec
import ndn.app_support.security_v2 as secv2
from Cryptodome.PublicKey import ECC
from Cryptodome.Random import get_random_bytes
# Generate trust anchor of admin
admin_priv = ECC.generate(curve='ed25519')
admin_pub = admin_priv.public_key()
admin_pub_bits = admin_pub.export_key(format='DER')
admin_identity_name = enc.Name.from_str('/lab/admin')
# Key name is <IdentityName>/KEY/<keyId>, here we use random value as key ID.
# You can also use timestamp, sequencial number, or hash.
admin_key_name = enc.Name.normalize(
admin_identity_name + ['KEY', enc.Component.from_bytes(get_random_bytes(8))])
# Self signer is used to sign the self-signed certificate only.
admin_self_signer = sec.Ed25519Signer(
admin_key_name, admin_priv.export_key(format='DER'))
anchor_name, anchor = secv2.self_sign(
admin_key_name, admin_pub_bits, admin_self_signer)
# We create a new signer with this certificate's name for further use
admin_signer = sec.Ed25519Signer(
anchor_name, admin_priv.export_key(format='DER'))
print(f'Admin trust anchor name: {enc.Name.to_str(anchor_name)}')
print(f'Admin trust anchor hex: {bytes(anchor).hex()}')
print()
# Generate the student's key and issue him a certificate
stu_priv = ECC.generate(curve='ed25519')
stu_pub = stu_priv.public_key()
stu_pub_bits = stu_pub.export_key(format='DER')
stu_identity_name = enc.Name.from_str('/lab/student/xinyu.ma')
stu_key_name = enc.Name.normalize(
stu_identity_name + ['KEY', enc.Component.from_bytes(get_random_bytes(8))])
stu_cert_name, stu_cert = secv2.new_cert(
stu_key_name,
'admin',
stu_pub_bits,
admin_signer,
start_time=datetime.now(timezone.utc),
end_time=datetime.now(timezone.utc) + timedelta(days=365),
)
stu_signer = sec.Ed25519Signer(
stu_cert_name, stu_priv.export_key(format='DER'))
print(f'Student certificate name: {enc.Name.to_str(stu_cert_name)}')
print(f'Student certificate hex: {bytes(stu_cert).hex()}')
print()
# Sign the paper's data with the student's certificate
data_wire = enc.make_data(
name='/lab/paper/ndn/change/1',
meta_info=enc.MetaInfo(freshness_period=10000),
content=b'Hello, NDN!',
signer=stu_signer
)
print('Data:', data_wire.hex())
import { Component, Data, Name, ValidityPeriod } from '@ndn/packet';
import { Encoder } from '@ndn/tlv';
import { toHex, toUtf8 } from '@ndn/util';
import { Certificate, Ed25519, generateSigningKey } from '@ndn/keychain';
// Generate trust anchor of admin
const adminIdentityName = new Name('/lab/admin');
const [adminSelfSigner, adminVerifier] = await generateSigningKey(adminIdentityName, Ed25519);
const anchor = await Certificate.selfSign({
privateKey: adminSelfSigner,
publicKey: adminVerifier,
});
const adminSigner = adminSelfSigner.withKeyLocator(anchor.name);
const anchorWire = Encoder.encode(anchor.data);
console.log(`Admin trust anchor name: ${anchor.name.toString()}`);
console.log(`Admin trust anchor hex: ${toHex(anchorWire)}`);
console.log('');
// The following line create the verifier from certificate
// const anchor = Certificate.fromData(Decoder.decode(anchorWire, Data));
// const adminVerifier = await createVerifier(anchor, {algoList: [Ed25519]});
// Generate the student's key and issue him a certificate
const stuIdentityName = new Name('/lab/student/xinyu.ma');
const [stuSelfSigner, stuVerifier] = await generateSigningKey(stuIdentityName, Ed25519);
const stuCert = await Certificate.issue({
issuerPrivateKey: adminSigner,
publicKey: stuVerifier,
issuerId: new Component(8, 'admin'),
// Equivalent to the following:
// validity: new ValidityPeriod(Date.now(), Date.now() + 365 * 86400000),
validity: ValidityPeriod.daysFromNow(365),
});
const stuSigner = stuSelfSigner.withKeyLocator(stuCert.name);
const stuCertWire = Encoder.encode(stuCert.data);
console.log(`Student certificate name: ${stuCert.name.toString()}`);
console.log(`Student certificate hex: ${toHex(stuCertWire)}`);
console.log('');
// Sign the paper's data with the student's certificate
const data = new Data(
new Name('/lab/paper/ndn/change/1'),
Data.FreshnessPeriod(10000),
toUtf8('Hello, NDN!'),
);
await stuSigner.sign(data);
const dataWire = Encoder.encode(data);
console.log('Data:', toHex(dataWire));
An application may involve multiple trust domains. Inter-domain trust relations will be established by the controller. In the case where pure peer-to-peer trust relation is established, every member is its own controller.
Trust Zone
A trust domain has also been called a trust zone in some papers, not to be confused with the hardware trust zone.
Trust Schema
TBD
Full Name
The full name of a Data packet is the name of the Data appended with a ImplicitSha256Digest component. The digest component contains a SHA256 hash of the whole packet. Therefore, if the consumer securely obtains the full name of a packet, it can verifies the integrity by simply computing its hash digest, without knowing any key.
This is useful for bootstrapping: one can learn the full name of the new member's certificate out-of-band, and then fetch the it through some non-secure channel.
(Code snippet to be added)