-
Notifications
You must be signed in to change notification settings - Fork 4
/
v4_public.go
135 lines (100 loc) · 3.6 KB
/
v4_public.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package pvx
import (
"crypto/ed25519"
"fmt"
"strings"
)
const (
headerV4Public = "v4.public."
)
// ProtoV4Public is a public purpose of PASETO which supports token signing and verification.
type ProtoV4Public struct{}
// NewPV4Public is a constructor-like sugar for ProtoV4Public.
func NewPV4Public() *ProtoV4Public { return &ProtoV4Public{} }
// PV4Public can be used as a global reference for protocol version 4 with public purpose.
var PV4Public = NewPV4Public()
// Sign signs claims with private key, authenticating its content but still preserving in plaintext.
func (pv4 *ProtoV4Public) Sign(sk *AsymSecretKey, claims Claims, ops ...ProvidedOption) (string, error) {
if !sk.isValidFor(Version4, purposePublic) {
return "", ErrWrongKey
}
opts := &optional{}
for i := range ops {
err := ops[i](opts)
if err != nil {
return "", err
}
}
payload, optionalFooter, err := encode(claims, opts.footer)
if err != nil {
return "", err
}
return pv4.sign(sk.keyMaterial, payload, optionalFooter, opts.assertion)
}
func (pv4 *ProtoV4Public) sign(sk ed25519.PrivateKey, message, optionalFooter []byte, assertion []byte) (string, error) {
if l := len(sk); l != ed25519.PrivateKeySize {
return "", fmt.Errorf("bad private key length, need %d bytes, provided %d bytes", ed25519.PrivateKeySize, l)
}
// step 1
const header = headerV4Public
// step 2
m2 := preAuthenticationEncoding([]byte(header), message, optionalFooter, assertion)
// step 3
sig := ed25519.Sign(sk, m2)
// step 4
messageWithSignature := append(message, sig...)
b64MessageWithSignature := b64(messageWithSignature)
emptyFooter := len(optionalFooter) == 0
var b64Footer string
if !emptyFooter {
b64Footer = b64(optionalFooter)
}
var token string
if emptyFooter {
token = strings.Join([]string{headerV4Version, headerPurposePublic, b64MessageWithSignature}, ".")
} else {
token = strings.Join([]string{headerV4Version, headerPurposePublic, b64MessageWithSignature, b64Footer}, ".")
}
return token, nil
}
// Verify just verifies token returning its structure for subsequent mapping.
func (pv4 *ProtoV4Public) Verify(token string, asymmetricPublicKey *AsymPublicKey, ops ...ProvidedOption) *Token {
if !asymmetricPublicKey.isValidFor(Version4, purposePublic) {
return &Token{claims: nil, footer: nil, err: ErrWrongKey}
}
opts := &optional{}
for i := range ops {
err := ops[i](opts)
if err != nil {
return &Token{claims: nil, footer: nil, err: err}
}
}
claims, footer, err := pv4.verify(token, asymmetricPublicKey.keyMaterial, opts.assertion)
return &Token{claims: claims, footer: footer, err: err}
}
func (pv4 *ProtoV4Public) verify(token string, pk ed25519.PublicKey, assertion []byte) ([]byte, []byte, error) {
if l := len(pk); l != ed25519.PublicKeySize {
return nil, nil, fmt.Errorf("bad public key length, need %d bytes, provided %d", ed25519.PublicKeySize, l)
}
// step 2
const header = headerV4Public
if !strings.HasPrefix(token, header) {
return nil, nil, ErrMalformedToken
}
// step 3
bodyBytes, footerBytes, err := decodeB64ToRawBinary(token, len(header))
if err != nil {
return nil, nil, fmt.Errorf("failed to decode token: %w", err)
}
if len(bodyBytes) < ed25519.SignatureSize {
return nil, nil, fmt.Errorf("incorrect token size: %w", ErrMalformedToken)
}
signature, message := bodyBytes[len(bodyBytes)-ed25519.SignatureSize:], bodyBytes[:len(bodyBytes)-ed25519.SignatureSize]
// step 4
m2 := preAuthenticationEncoding([]byte(header), message, footerBytes, assertion)
// step 5
if !ed25519.Verify(pk, m2, signature) {
return nil, nil, ErrInvalidSignature
}
return message, footerBytes, nil
}