Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 92 additions & 1 deletion std/security/certificate.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
package security

import (
"crypto/sha256"
"fmt"
"io"
"time"

enc "github.com/named-data/ndnd/std/encoding"
"github.com/named-data/ndnd/std/ndn"
spec "github.com/named-data/ndnd/std/ndn/spec_2022"
revocationtlv "github.com/named-data/ndnd/std/security/revocation_tlv"
sig "github.com/named-data/ndnd/std/security/signer"
"github.com/named-data/ndnd/std/types/optional"
)

type revocationReason uint64

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use enum to enforce specific revocation reasons?


const revocationReasonUnspecified revocationReason = 0

const defaultRevocationFreshness = 8760 * time.Hour

// RevokeCertArgs are the arguments to RevokeCert.
type RevokeCertArgs struct {
// Cert is the certificate being revoked.
Cert ndn.Data
// Signer signs the revocation record Data packet. May be nil for unsigned records.
Signer ndn.Signer
// Reason is the revocation reason code.
Reason revocationReason
// Timestamp is the revocation timestamp. Defaults to now.
Timestamp optional.Optional[time.Time]
// NotBefore marks data produced before this timestamp as still valid.
NotBefore optional.Optional[time.Time]
// Freshness is the FreshnessPeriod of the record Data packet.
Freshness optional.Optional[time.Duration]
}

// SignCertArgs are the arguments to SignCert.
type SignCertArgs struct {
// Signer is the private key used to sign the certificate.
Expand Down Expand Up @@ -97,7 +121,7 @@ func SelfSign(args SignCertArgs) (wire enc.Wire, err error) {
return SignCert(args)
}

// (AI GENERATED DESCRIPTION): Returns true if the certificate’s signature is nil or its validity period does not include the current time.
// CertIsExpired reports whether the certificate is outside its validity period.
func CertIsExpired(cert ndn.Data) bool {
if cert.Signature() == nil {
return true
Expand All @@ -115,6 +139,73 @@ func CertIsExpired(cert ndn.Data) bool {
return false
}

// RevokeCert builds a signed revocation record Data packet for a certificate.
func RevokeCert(args RevokeCertArgs) (enc.Wire, error) {
if args.Cert == nil {
return nil, fmt.Errorf("certificate is nil")
}

recordName, ok := revocationRecordName(args.Cert)
if !ok {
return nil, fmt.Errorf("invalid certificate name for revocation: %s", args.Cert.Name())
}

timestamp := args.Timestamp.GetOr(time.Now())
hash := sha256.Sum256(args.Cert.Content().Join())
record := revocationtlv.RevocationRecord{
Timestamp: uint64(timestamp.UnixMilli()),
Reason: uint64(args.Reason),
PublicKeyHash: hash[:],
}
if nb, ok := args.NotBefore.Get(); ok {
record.NotBefore = optional.Some(uint64(nb.UnixMilli()))
}

// Create revocation record data
cfg := &ndn.DataConfig{
ContentType: optional.Some(ndn.ContentTypeKey),
Freshness: optional.Some(args.Freshness.GetOr(defaultRevocationFreshness)),
}

encoded, err := spec.Spec{}.MakeData(recordName, cfg, record.Encode(), args.Signer)
if err != nil {
return nil, err
}

return encoded.Wire, nil
}

func revocationRecordName(cert ndn.Data) (enc.Name, bool) {
if cert == nil {
return nil, false
}

certName := stripImplicitDigest(cert.Name())
identity, err := GetIdentityFromCertName(certName)
if err != nil || !certName.At(-1).IsVersion() {
return nil, false
}

recordName := make(enc.Name, len(certName), len(certName)+1)
copy(recordName, certName)
recordName[len(identity)] = enc.NewGenericComponent("REVOKE")
return recordName.Append(certName.At(-2)), true
}

func isRevocationRecordName(name enc.Name) bool {
name = stripImplicitDigest(name)
if len(name) < 6 || name.At(-1).Typ != enc.TypeGenericNameComponent || !name.At(-2).IsVersion() {
return false
}

certName := make(enc.Name, len(name)-1)
copy(certName, name.Prefix(-1))
certName[len(name)-5] = enc.NewGenericComponent("KEY")

_, err := GetIdentityFromCertName(certName)
return err == nil
}

// getPubKey gets the public key from an NDN data.
// returns [public key, key name, error].
func getPubKey(data ndn.Data) ([]byte, enc.Name, error) {
Expand Down
41 changes: 41 additions & 0 deletions std/security/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package security_test

import (
"encoding/base64"
"fmt"
"testing"
"time"

enc "github.com/named-data/ndnd/std/encoding"
"github.com/named-data/ndnd/std/ndn"
Expand Down Expand Up @@ -174,6 +176,45 @@ func TestSignCertWithSignerCertName(t *testing.T) {
require.True(t, tu.NoErr(signer.ValidateData(newCert, newSigCov, aliceCertData)))
}

func TestCertificateRevocationRecord(t *testing.T) {
tu.SetT(t)

aliceKey, _ := base64.StdEncoding.DecodeString(KEY_ALICE)
aliceKeyData, _, _ := spec_2022.Spec{}.ReadData(enc.NewBufferView(aliceKey))
aliceSigner := tu.NoErr(signer.UnmarshalSecret(aliceKeyData))

aliceCertWire := tu.NoErr(sec.SignCert(sec.SignCertArgs{
Signer: aliceSigner,
Data: aliceKeyData,
IssuerId: revocationTestIssuer(t),
NotBefore: T1,
NotAfter: T2,
}))
aliceCert, _, err := spec_2022.Spec{}.ReadData(enc.NewWireView(aliceCertWire))
require.NoError(t, err)

_, err = sec.RevokeCert(sec.RevokeCertArgs{Cert: nil})
require.Error(t, err)

recordWire, err := sec.RevokeCert(sec.RevokeCertArgs{
Cert: aliceCert,
Signer: aliceSigner,
})
require.NoError(t, err)
recordData, _, err := spec_2022.Spec{}.ReadData(enc.NewWireView(recordWire))
require.NoError(t, err)
require.Contains(t, recordData.Name().String(), "/REVOKE/")
contentType, ok := recordData.ContentType().Get()
require.True(t, ok)
require.Equal(t, ndn.ContentTypeKey, contentType)
require.NotEmpty(t, recordData.Content())
}

func revocationTestIssuer(t *testing.T) enc.Component {
t.Helper()
return enc.NewGenericComponent(fmt.Sprintf("%s-%d", t.Name(), time.Now().UnixNano()))
}

func TestEncodeDecodeCertList(t *testing.T) {
tu.SetT(t)
n1 := tu.NoErr(enc.NameFromStr("/ndn/alice/KEY/aa/self/v=1"))
Expand Down
18 changes: 18 additions & 0 deletions std/security/revocation_tlv/definitions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//go:generate gondn_tlv_gen
package revocationtlv

import "github.com/named-data/ndnd/std/types/optional"

// RevocationRecord content TLVs per UCLA-IRL/ndnrevoke.
//
// +tlv-model:ordered
type RevocationRecord struct {
//+field:natural
Timestamp uint64 `tlv:"0xC9"`
//+field:natural
Reason uint64 `tlv:"0xCB"`
//+field:binary
PublicKeyHash []byte `tlv:"0xCA"`
//+field:natural:optional
NotBefore optional.Optional[uint64] `tlv:"0xCD"`
}
Loading
Loading