-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathunwrapper.go
More file actions
129 lines (117 loc) · 4.97 KB
/
Copy pathunwrapper.go
File metadata and controls
129 lines (117 loc) · 4.97 KB
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
package fee
import (
"crypto/ecdh"
"fmt"
"github.com/filecoin-project/go-fee/aeskw"
"github.com/filecoin-project/go-fee/cose"
"github.com/filecoin-project/go-fee/ecdhkw"
)
// RecipientUnwrapper recovers a content-encryption key (CEK) from the in-envelope
// recipient that holds it. [Decrypt] uses keyID to find the matching recipient
// entry, then calls unwrap on it. (When the CEK is supplied out of band rather
// than carried in the envelope, use [DecryptWithCEK] and no unwrapper.)
//
// RecipientUnwrapper is a sealed interface: the only implementations are the ones
// constructed by [NewECDHESUnwrapper] and [NewA256KWUnwrapper], mirroring the
// wrap side.
type RecipientUnwrapper interface {
// keyID returns the kid this unwrapper recovers CEKs for. Decrypt matches
// it, by exact bytes, against each recipient entry's kid.
keyID() []byte
// unwrap recovers the CEK from the recipient entry whose kid matched. It
// returns an error if the entry's key-wrap algorithm is not the one this
// unwrapper handles, or if the wrapped CEK cannot be recovered.
unwrap(r *cose.Recipient) ([]byte, error)
}
// ecdhESUnwrapper recovers a CEK wrapped with ECDH-ES+A256KW using an X25519
// private key.
type ecdhESUnwrapper struct {
kid []byte
priv *ecdh.PrivateKey
}
// NewECDHESUnwrapper returns a [RecipientUnwrapper] that recovers a CEK wrapped
// to the X25519 public key matching priv. kid is the recipient key id to match —
// the same value passed to [NewECDHESRecipient]. priv must be an X25519 key.
func NewECDHESUnwrapper(kid []byte, priv *ecdh.PrivateKey) RecipientUnwrapper {
return &ecdhESUnwrapper{kid: kid, priv: priv}
}
func (u *ecdhESUnwrapper) keyID() []byte { return u.kid }
func (u *ecdhESUnwrapper) unwrap(r *cose.Recipient) ([]byte, error) {
alg, ok := r.Headers.Protected.Int(cose.HeaderLabelAlg)
if !ok {
return nil, fmt.Errorf("%w: recipient algorithm header missing or not an integer", ErrUnsupportedRecipientAlg)
}
if alg != cose.AlgECDHESA256KW {
return nil, fmt.Errorf("%w: recipient algorithm %d is not ECDH-ES+A256KW", ErrUnsupportedRecipientAlg, alg)
}
ephPub, err := ephemeralX25519(r.Headers.Unprotected)
if err != nil {
return nil, err
}
cek, err := ecdhkw.Unwrap(u.priv, &ecdhkw.Wrapped{
EphemeralPublicKey: ephPub,
WrappedCEK: r.Ciphertext,
})
if err != nil {
return nil, fmt.Errorf("fee: ECDH-ES+A256KW unwrap: %w", err)
}
return cek, nil
}
// ephemeralX25519 extracts the sender's ephemeral X25519 public key from a
// recipient's ephemeral-key header. The ephemeral key is a self-describing
// COSE_Key map (see the coseKey* constants in recipient.go): its kty must be OKP
// and its crv X25519, and the public key travels in the x parameter (label -2).
// A missing header, a non-COSE_Key value, a mismatched key type or curve, or an
// invalid point is reported as [ErrMalformedEnvelope].
func ephemeralX25519(unprotected cose.Header) (*ecdh.PublicKey, error) {
raw, ok := unprotected.Get(cose.HeaderLabelEphemeralKey)
if !ok {
return nil, fmt.Errorf("%w: ECDH-ES recipient missing ephemeral key", ErrMalformedEnvelope)
}
key, ok := raw.(map[any]any)
if !ok {
return nil, fmt.Errorf("%w: ECDH-ES ephemeral key is not a COSE_Key map", ErrMalformedEnvelope)
}
if kty, ok := key[coseKeyLabelKty].(int64); !ok || kty != coseKeyTypeOKP {
return nil, fmt.Errorf("%w: ECDH-ES ephemeral key type is not OKP", ErrMalformedEnvelope)
}
if crv, ok := key[coseKeyLabelCrv].(int64); !ok || crv != coseKeyCurveX25519 {
return nil, fmt.Errorf("%w: ECDH-ES ephemeral key curve is not X25519", ErrMalformedEnvelope)
}
x, ok := key[coseKeyLabelXCoord].([]byte)
if !ok {
return nil, fmt.Errorf("%w: ECDH-ES ephemeral key missing x coordinate", ErrMalformedEnvelope)
}
pub, err := ecdh.X25519().NewPublicKey(x)
if err != nil {
return nil, fmt.Errorf("%w: ECDH-ES ephemeral key: %v", ErrMalformedEnvelope, err)
}
return pub, nil
}
// a256kwUnwrapper recovers a CEK wrapped with A256KW under a symmetric KEK.
type a256kwUnwrapper struct {
kid []byte
kek []byte
}
// NewA256KWUnwrapper returns a [RecipientUnwrapper] that recovers a CEK wrapped
// under kek with A256KW. kid is the recipient key id to match — the same value
// passed to [NewA256KWRecipient]. kek must be the 32-byte KEK the CEK was wrapped
// under; a different KEK fails the unwrap with [aeskw.ErrIntegrity].
func NewA256KWUnwrapper(kid, kek []byte) RecipientUnwrapper {
return &a256kwUnwrapper{kid: kid, kek: kek}
}
func (u *a256kwUnwrapper) keyID() []byte { return u.kid }
func (u *a256kwUnwrapper) unwrap(r *cose.Recipient) ([]byte, error) {
alg, ok := r.Headers.Protected.Int(cose.HeaderLabelAlg)
if !ok {
return nil, fmt.Errorf("%w: recipient algorithm header missing or not an integer", ErrUnsupportedRecipientAlg)
}
if alg != cose.AlgA256KW {
return nil, fmt.Errorf("%w: recipient algorithm %d is not A256KW", ErrUnsupportedRecipientAlg, alg)
}
cek, err := aeskw.Unwrap(u.kek, r.Ciphertext)
if err != nil {
return nil, fmt.Errorf("fee: A256KW unwrap: %w", err)
}
return cek, nil
}