Skip to content

Commit

Permalink
ndncert: implement pin challenge
Browse files Browse the repository at this point in the history
  • Loading branch information
pulsejet committed Jan 28, 2025
1 parent f092fbd commit 096f895
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 46 deletions.
4 changes: 4 additions & 0 deletions std/security/ndncert/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const (
ChallengeStatusFailure ChallengeStatus = 4
)

const KwEmail = "email"
const KwPin = "pin"
const KwCode = "code"

var ErrChallengeBefore = errors.New("challenge before request")
var ErrChallengePending = errors.New("challenge pending")
var ErrChallengeFailed = errors.New("challenge failed")
Expand Down
6 changes: 3 additions & 3 deletions std/security/ndncert/challenge_email.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type ChallengeEmail struct {
}

func (*ChallengeEmail) Name() string {
return "email"
return KwEmail
}

func (c *ChallengeEmail) Request(input ParamMap, status *string) (ParamMap, error) {
Expand All @@ -24,7 +24,7 @@ func (c *ChallengeEmail) Request(input ParamMap, status *string) (ParamMap, erro
// Initial request parameters
if input == nil {
return ParamMap{
"email": []byte(c.Email),
KwEmail: []byte(c.Email),
}, nil
}

Expand All @@ -36,7 +36,7 @@ func (c *ChallengeEmail) Request(input ParamMap, status *string) (ParamMap, erro
}

return ParamMap{
"code": []byte(code),
KwCode: []byte(code),
}, nil
}

Expand Down
41 changes: 41 additions & 0 deletions std/security/ndncert/challenge_pin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ndncert

import (
"errors"
)

type ChallengePin struct {
// Callback to get the code from the user.
CodeCallback func(status string) string
}

func (*ChallengePin) Name() string {
return KwPin
}

func (c *ChallengePin) Request(input ParamMap, status *string) (ParamMap, error) {
// Validate challenge configuration
if c.CodeCallback == nil {
return nil, errors.New("pin challenge not configured")
}

// Initial request parameters
if input == nil {
return ParamMap{}, nil
}

// Challenge response code
if status != nil && (*status == "need-code" || *status == "wrong-code") {
code := c.CodeCallback(*status)
if code == "" {
return nil, errors.New("no code provided")
}

return ParamMap{
KwCode: []byte(code),
}, nil
}

// Unknown status
return nil, errors.New("unknown input to pin challenge")
}
18 changes: 9 additions & 9 deletions std/security/ndncert/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ func (c *Client) SetSigner(signer ndn.Signer) {
c.signer = signer
}

// CaPrefix returns the CA prefix.
func (c *Client) CaPrefix() enc.Name {
return c.caPrefix
}

// FetchProfile fetches the profile from the CA (blocking).
func (c *Client) FetchProfile() (*tlv.CaProfile, error) {
// TODO: validate packets received by the client using the cert.
Expand All @@ -106,12 +111,7 @@ func (c *Client) FetchProfile() (*tlv.CaProfile, error) {
}

// FetchProbe sends a PROBE request to the CA (blocking).
func (c *Client) FetchProbe(challenge Challenge) (*tlv.ProbeRes, error) {
params, err := challenge.Request(nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get challenge params: %w", err)
}

func (c *Client) FetchProbe(params ParamMap) (*tlv.ProbeRes, error) {
probeParams := tlv.ProbeReq{Params: params}

ch := make(chan ndn.ExpressCallbackArgs, 1)
Expand Down Expand Up @@ -148,9 +148,9 @@ func (c *Client) FetchProbe(challenge Challenge) (*tlv.ProbeRes, error) {

// FetchProbeRedirect sends a PROBE request to the CA (blocking).
// If a redirect is received, the request is sent to the new location.
func (c *Client) FetchProbeRedirect(challenge Challenge) (probe *tlv.ProbeRes, err error) {
func (c *Client) FetchProbeRedirect(params ParamMap) (probe *tlv.ProbeRes, err error) {
for i := 0; i < 4; i++ {
probe, err = c.FetchProbe(challenge)
probe, err = c.FetchProbe(params)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -256,7 +256,7 @@ func (c *Client) New(challenge Challenge, expiry time.Time) (*tlv.NewRes, error)
break
}
}
if !hasChallenge {
if !hasChallenge && challenge.Name() != KwPin { // pin is always supported
return nil, fmt.Errorf("challenge not supported by CA: %s", challenge.Name())
}

Expand Down
108 changes: 74 additions & 34 deletions tools/cert_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/named-data/ndnd/std/ndn"
sec "github.com/named-data/ndnd/std/security"
"github.com/named-data/ndnd/std/security/ndncert"
"github.com/named-data/ndnd/std/security/signer"
spec_ndncert "github.com/named-data/ndnd/std/security/ndncert/tlv"
sig "github.com/named-data/ndnd/std/security/signer"
)

Expand All @@ -24,6 +24,7 @@ type CertClient struct {
keyFile string
outFile string
challenge string
email string
}

caCert []byte
Expand Down Expand Up @@ -54,6 +55,7 @@ func (c *CertClient) run() {
flagset.StringVar(&c.opts.outFile, "o", "", "Output filename without extension (default: stdout)")
flagset.StringVar(&c.opts.keyFile, "k", "", "File with NDN key to certify (default: generate new key)")
flagset.StringVar(&c.opts.challenge, "c", "", "Challenge type (default: ask)")
flagset.StringVar(&c.opts.email, "email", "", "Email address for probe and email challenge")
flagset.Parse(c.args[1:])

argCaCert := flagset.Arg(0)
Expand Down Expand Up @@ -102,16 +104,20 @@ func (c *CertClient) run() {
func (c *CertClient) chooseChallenge() ndncert.Challenge {
defer fmt.Fprintln(os.Stderr)

challenges := []string{"email", "pin"}
challenges := []string{ndncert.KwEmail, ndncert.KwPin}

if c.opts.challenge == "" {
i := c.chooseOpts("Please choose a challenge type:", challenges)
c.opts.challenge = challenges[i]
}

switch c.opts.challenge {
case "email":
email := &ndncert.ChallengeEmail{
case ndncert.KwEmail:
if c.opts.email == "" {
c.scanln("Enter your email address", &c.opts.email)
}
return &ndncert.ChallengeEmail{
Email: c.opts.email,
CodeCallback: func(status string) (code string) {
fmt.Fprintf(os.Stderr, "\n")
fmt.Fprintf(os.Stderr, "Challenge Status: %s\n", status)
Expand All @@ -120,12 +126,17 @@ func (c *CertClient) chooseChallenge() ndncert.Challenge {
return code
},
}
fmt.Fprintf(os.Stderr, "Enter your email address: ")
fmt.Scanln(&email.Email)
return email

case "pin":
panic("PIN challenge not implemented")
case ndncert.KwPin:
return &ndncert.ChallengePin{
CodeCallback: func(status string) (code string) {
fmt.Fprintf(os.Stderr, "\n")
fmt.Fprintf(os.Stderr, "Challenge Status: %s\n", status)
fmt.Fprintf(os.Stderr, "Enter the secret PIN: ")
fmt.Scanln(&code)
return code
},
}

default:
fmt.Fprintf(os.Stderr, "Invalid challenge selected: %s\n", c.opts.challenge)
Expand All @@ -152,39 +163,49 @@ func (c *CertClient) client() {
return
}

// Probe the CA and redirect to the correct CA
probe, err := certClient.FetchProbeRedirect(c.challenge)
// Fetch root CA profile
caprefix := certClient.CaPrefix()
profile, err := certClient.FetchProfile()
if err != nil {
log.Fatal(c, "Unable to probe the CA", "err", err)
log.Fatal(c, "Unable to fetch CA profile", "err", err)
return
}
c.printCaProfile(profile)

// We expect all CAs to support the same param keys for now.
// This is a reasonable assumption (for now) at least on testbed.
probeParams := ndncert.ParamMap{}
for _, paramKey := range profile.ParamKey {
switch paramKey {
case ndncert.KwEmail:
if c.opts.email == "" {
c.scanln("Enter email address for PROBE", &c.opts.email)
}
probeParams[paramKey] = []byte(c.opts.email)

// Fetch CA profile
profile, err := certClient.FetchProfile()
default:
var paramVal string
c.scanln(fmt.Sprintf("Enter PROBE param '%s'", paramKey), &paramVal)
probeParams[paramKey] = []byte(paramVal)
}
}

// Probe the CA and redirect to the correct CA
probe, err := certClient.FetchProbeRedirect(probeParams)
if err != nil {
log.Fatal(c, "Unable to fetch CA profile", "err", err)
log.Fatal(c, "Unable to probe the CA", "err", err)
return
}

fmt.Fprintln(os.Stderr, "================ CA Profile ===============")
fmt.Fprintln(os.Stderr, profile.CaInfo)
fmt.Fprintln(os.Stderr, "Name:", profile.CaPrefix.Name)
fmt.Fprintln(os.Stderr, "Max Validity:", time.Duration(profile.MaxValidPeriod)*time.Second)
fmt.Fprintln(os.Stderr, "Challenges:", profile.ParamKey)
fmt.Fprintln(os.Stderr, "===========================================")
fmt.Fprintln(os.Stderr)

// Check if the challenge we selected is supported
found := false
for _, ch := range profile.ParamKey {
if ch == c.challenge.Name() {
found = true
break
// Fetch redirected CA profile if changed
if !certClient.CaPrefix().Equal(caprefix) {
fmt.Fprintf(os.Stderr, "Redirected to CA: %s\n\n", certClient.CaPrefix())
profile, err = certClient.FetchProfile()
if err != nil {
log.Fatal(c, "Unable to fetch CA profile", "err", err)
return
}
}
if !found {
fmt.Fprintf(os.Stderr, "Selected challenge is not supported by the CA\n")
os.Exit(1)
c.printCaProfile(profile)
}

// If a key is provided, check if the name matches
Expand Down Expand Up @@ -282,7 +303,7 @@ func (c *CertClient) client() {
// Marshal the key if not specified as file
var keyBytes []byte = nil
if c.opts.keyFile == "" {
keyWire, err := signer.MarshalSecret(c.signer)
keyWire, err := sig.MarshalSecret(c.signer)
if err != nil {
log.Fatal(c, "Unable to marshal key", "err", err)
return
Expand Down Expand Up @@ -347,3 +368,22 @@ func (c *CertClient) chooseOpts(msg string, opts []string) int {

return c.chooseOpts(msg, opts)
}

func (c *CertClient) printCaProfile(profile *spec_ndncert.CaProfile) {
fmt.Fprintln(os.Stderr, "=============== CA Profile ================")
fmt.Fprintln(os.Stderr, profile.CaInfo)
fmt.Fprintln(os.Stderr, "Name:", profile.CaPrefix.Name)
fmt.Fprintln(os.Stderr, "Max Validity:", time.Duration(profile.MaxValidPeriod)*time.Second)
fmt.Fprintln(os.Stderr, "Probe Keys:", profile.ParamKey)
fmt.Fprintln(os.Stderr, "===========================================")
fmt.Fprintln(os.Stderr)
}

func (c *CertClient) scanln(msg string, val *string) {
fmt.Fprintf(os.Stderr, "%s: ", msg)
fmt.Scanln(val)
if *val == "" {
fmt.Fprintf(os.Stderr, "Invalid value entered\n")
os.Exit(3)
}
}

0 comments on commit 096f895

Please sign in to comment.