Skip to content

Commit b3673b3

Browse files
authored
Add renewal token verification (#20)
* Add renewal token verification * Add tests * Add renew_identity claim to the root cert * fix * Update cert and privkey * Add negative test
1 parent 6055d79 commit b3673b3

File tree

5 files changed

+208
-26
lines changed

5 files changed

+208
-26
lines changed

api/signing.pb.go

Lines changed: 31 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/signing.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ enum FlagClaim {
4848
// Cert has the authority to sign GovalToken messages that can prove identity.
4949
IDENTITY = 5;
5050

51+
// Cert has ability to mint Repl Identity tokens
52+
RENEW_IDENTITY = 7;
53+
54+
// Cert has ability to mint Repl KV tokens
55+
RENEW_KV = 8;
56+
5157
// Cert has the authority to sign GovalToken messages that authorizes the
5258
// bearer to use Ghostwriter.
5359
GHOSTWRITER = 6;

identity_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,57 @@ func Example() {
6060
replIdentity.Aud,
6161
)
6262
}
63+
64+
func ExampleRenew() {
65+
identity := os.Getenv("REPL_RENEWAL")
66+
if identity == "" {
67+
fmt.Println("Sorry, this repl does not yet have an identity (anonymous run?).")
68+
return
69+
}
70+
identityKey := os.Getenv("REPL_RENEWAL_KEY")
71+
if identity == "" {
72+
fmt.Println("Sorry, this repl does not yet have an identity (anonymous run?).")
73+
return
74+
}
75+
76+
// This should be set to the Repl ID of the repl you want to prove your
77+
// identity to.
78+
targetRepl := "target_repl"
79+
80+
// Create a signing authority that is authorized to emit tokens for the
81+
// current repl.
82+
signingAuthority, err := replidentity.NewSigningAuthority(
83+
string(identityKey),
84+
identity,
85+
os.Getenv("REPL_ID"),
86+
replidentity.ReadPublicKeyFromEnv,
87+
)
88+
if err != nil {
89+
panic(err)
90+
}
91+
92+
signedToken, err := signingAuthority.Sign(targetRepl)
93+
if err != nil {
94+
panic(err)
95+
}
96+
97+
// Verify the signed token, pretending we are the target repl.
98+
replIdentity, err := replidentity.VerifyRenewIdentity(
99+
signedToken,
100+
targetRepl,
101+
replidentity.ReadPublicKeyFromEnv,
102+
)
103+
if err != nil {
104+
panic(err)
105+
}
106+
107+
fmt.Println()
108+
fmt.Printf("The identity in the repl's token (%d bytes) is:\n", len(identity))
109+
fmt.Printf(
110+
"repl id: %s\n user: %s\n slug: %s audience: %s\n",
111+
replIdentity.Replid,
112+
replIdentity.User,
113+
replIdentity.Slug,
114+
replIdentity.Aud,
115+
)
116+
}

sign_test.go

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import (
2121
const (
2222
developmentKeyID = "dev:1"
2323
developmentPublicKey = "on0FkSmEC+ce40V9Vc4QABXSx6TXo+lhp99b6Ka0gro="
24-
conmanPrivateKey = "jF/ctKF4x9mL+s8e8bLe2R0Dmzr4FQ+hwBIm6U7TuBRrujSaTKHTWbXSoUc2Y0uQ3mm2YqOmQeR/isCd0qJrVw=="
25-
conmanCertificate = "GAEiBmNvbm1hbhK7AnYyLnB1YmxpYy5RMmQzU1hoMUsyZHNaMWxSZFdVM2NXNVJSVk5FUVdwV01YRlhiRUpvUkdRclpYRmtRVkp2UTBkQlJXRkJhR2RHUjJkSldVRm9iME5IUVUxaFFXaG5SVWxxVm5KTmFUVjNaRmRLYzJGWFRYVlpWR1IyVFVjeGNtVlhaM2ROVnpCNFRVaEdSMU5GTlhSVWEzaHlWR3BXZDJSSE1VeGhia0p5VTBkMGJVNUlTa0ppYlZKTVlWZEZlRmwzUFQxX1g4OVJiVDhTQlhLUUVpVzZfLXBFYUtUX2l1T3lnalB1QVhxajRZSDNHT1FiRlVSY08ydTRFZWF2SUJWU09oRHV4VF82NHFwZG4ydWkxcGo2RjRzTy5SMEZGYVVKdFRuWmliVEZvWW1kdlJscEhWakpQYWtVOQ=="
24+
conmanPrivateKey = "iRnuDeX+Dxum5UR8KMZbhtgUQ55PNOHzhJ5/RwqtiQw8AO6GbqzhvRRPB6SfYrev568iOAyJsiVgtdyiOB6YTQ=="
25+
conmanCertificate = "GAEiBmNvbm1hbhLGAnYyLnB1YmxpYy5RMmR6U1d4bWRVaHZVVmxST1RaeVdsbFNTVXhEUzFScGFreEJSMFZLUTNneVYwVmhRV2huUWtkblNWbENVbTlEUjBGallVRm9aMGxIWjBsWlFXaHZRMGRCVFdGQmFHZEZTV3BXY2sxcE5YZGtWMHB6WVZkTmRWVkZSa1ZrVjJoMFRtNU5NRmxxUWxaV1NHUnNZVEkwZVZONlRubE1WMVl5VTFkd2JsUlhiR2xUVjNoYVZFWm9hbUl5Y0c1YVZ6RkdUVUU5UFhaTklYOURrRFlocGk0SE9jMng2V2lNdV9qY3Y3MEFQd3A2Q2ZpbU1oejVjYWJoU0wzckdqQjN1cDBaSVp5M0tzWW9NelJSNjRtNTFLRmZqbVFNQkFvLlIwRkZhVUp0VG5aaWJURm9ZbWR2UmxwSFZqSlBha1U5"
2626
)
2727

2828
func generateIntermediateCert(
@@ -98,6 +98,23 @@ func identityToken(
9898
)
9999
}
100100

101+
func renewalToken(
102+
replID string,
103+
user string,
104+
slug string,
105+
) (ed25519.PrivateKey, string, error) {
106+
return tokenWithClaims(
107+
replID,
108+
user,
109+
slug,
110+
[]*api.CertificateClaim{
111+
{Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_RENEW_IDENTITY}},
112+
{Claim: &api.CertificateClaim_Replid{Replid: replID}},
113+
{Claim: &api.CertificateClaim_User{User: user}},
114+
},
115+
)
116+
}
117+
101118
func tokenWithClaims(
102119
replID string,
103120
user string,
@@ -232,6 +249,7 @@ func identityTokenAnyRepl(
232249
&conmanAuthority,
233250
[]*api.CertificateClaim{
234251
{Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_IDENTITY}},
252+
{Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_RENEW_IDENTITY}},
235253
{Claim: &api.CertificateClaim_Flag{Flag: api.FlagClaim_ANY_REPLID}},
236254
{Claim: &api.CertificateClaim_User{User: replIdentity.User}},
237255
},
@@ -579,3 +597,83 @@ func TestAnyReplIDIdentity(t *testing.T) {
579597
assert.Equal(t, "slug", replIdentity.Slug)
580598
assert.Equal(t, int64(1), replIdentity.UserId)
581599
}
600+
601+
func TestRenew(t *testing.T) {
602+
privkey, identity, err := renewalToken("repl", "user", "slug")
603+
require.NoError(t, err)
604+
605+
getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) {
606+
if keyid != developmentKeyID {
607+
return nil, nil
608+
}
609+
keyBytes, err := base64.StdEncoding.DecodeString(developmentPublicKey)
610+
if err != nil {
611+
return nil, fmt.Errorf("failed to parse public key as base64: %w", err)
612+
}
613+
614+
return ed25519.PublicKey(keyBytes), nil
615+
}
616+
617+
signingAuthority, err := NewSigningAuthority(
618+
string(paserk.PrivateKeyToPASERKSecret(privkey)),
619+
identity,
620+
"repl",
621+
getPubKey,
622+
)
623+
require.NoError(t, err)
624+
forwarded, err := signingAuthority.Sign("testing")
625+
require.NoError(t, err)
626+
627+
replIdentity, err := VerifyRenewIdentity(
628+
forwarded,
629+
"testing",
630+
getPubKey,
631+
)
632+
require.NoError(t, err)
633+
634+
assert.Equal(t, "repl", replIdentity.Replid)
635+
assert.Equal(t, "user", replIdentity.User)
636+
assert.Equal(t, "slug", replIdentity.Slug)
637+
}
638+
639+
func TestRenewNoClaim(t *testing.T) {
640+
privkey, identity, err := tokenWithClaims(
641+
"replid",
642+
"user",
643+
"slug",
644+
[]*api.CertificateClaim{
645+
{Claim: &api.CertificateClaim_Replid{Replid: "replid"}},
646+
{Claim: &api.CertificateClaim_User{User: "user"}},
647+
},
648+
)
649+
require.NoError(t, err)
650+
651+
getPubKey := func(keyid, issuer string) (ed25519.PublicKey, error) {
652+
if keyid != developmentKeyID {
653+
return nil, nil
654+
}
655+
keyBytes, err := base64.StdEncoding.DecodeString(developmentPublicKey)
656+
if err != nil {
657+
return nil, fmt.Errorf("failed to parse public key as base64: %w", err)
658+
}
659+
660+
return ed25519.PublicKey(keyBytes), nil
661+
}
662+
663+
signingAuthority, err := NewSigningAuthority(
664+
string(paserk.PrivateKeyToPASERKSecret(privkey)),
665+
identity,
666+
"replid",
667+
getPubKey,
668+
)
669+
require.NoError(t, err)
670+
forwarded, err := signingAuthority.Sign("testing")
671+
require.NoError(t, err)
672+
673+
_, err = VerifyRenewIdentity(
674+
forwarded,
675+
"testing",
676+
getPubKey,
677+
)
678+
require.Error(t, err)
679+
}

verify.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,23 @@ func VerifyIdentity(message string, audience string, getPubKey PubKeySource, opt
259259
return VerifyToken(opts)
260260
}
261261

262+
// VerifyRenewIdentity verifies that the given `REPL_RENEWAL` value is in fact
263+
// signed by Goval's chain of authority, addressed to the provided audience
264+
// (the `REPL_ID` of the recipient), and has the capability to renew the
265+
// identity.
266+
//
267+
// The optional options allow specifying additional verifications on the identity.
268+
func VerifyRenewIdentity(message string, audience string, getPubKey PubKeySource, options ...VerifyOption) (*api.GovalReplIdentity, error) {
269+
opts := VerifyTokenOpts{
270+
Message: message,
271+
Audience: audience,
272+
GetPubKey: getPubKey,
273+
Options: options,
274+
Flags: []api.FlagClaim{api.FlagClaim_RENEW_IDENTITY},
275+
}
276+
return VerifyToken(opts)
277+
}
278+
262279
type VerifyTokenOpts struct {
263280
Message string
264281
Audience string

0 commit comments

Comments
 (0)