Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 1 | // Package caveat provides some third-party caveat implementations for the Google runtime. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 2 | package caveat |
| 3 | |
| 4 | import ( |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 5 | "bytes" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 6 | "crypto/ecdsa" |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 7 | "crypto/rand" |
| 8 | "crypto/sha256" |
| 9 | "encoding/binary" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 10 | "fmt" |
| 11 | "math/big" |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 12 | "time" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 13 | |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 14 | vcaveat "veyron/security/caveat" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 15 | "veyron2/security" |
Asim Shankar | 8303f18 | 2014-06-05 00:42:33 -0700 | [diff] [blame] | 16 | "veyron2/security/wire" |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 17 | "veyron2/vom" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 18 | ) |
| 19 | |
| 20 | // nonceLength specifies the length in bytes of the random nonce used |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 21 | // in publicKeyCaveat and publicKeyDischarge. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 22 | // |
| 23 | // TODO(ataly, ashankar): Nonce length of 16 bytes was chosen very conservatively. |
| 24 | // The purpose of the random nonce is essentially to separate caveats that have |
| 25 | // the same restriction and validation key (in order to prevent discharge replay). |
| 26 | // Can we use 4bytes instead?. |
| 27 | const nonceLength = 16 |
| 28 | |
| 29 | // errPublicKeyCaveat returns an error indicating that the provided caveat has |
| 30 | // an invalid or missing discharge. |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 31 | func errPublicKeyCaveat(c *publicKeyCaveat, err error) error { |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 32 | return fmt.Errorf("%v for publicKeyCaveat{nonce:%v location:%q}", err, c.RandNonce, c.Location()) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 33 | } |
| 34 | |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 35 | // publicKeyCaveat implements security.ThirdPartyCaveat. It specifies a restriction, |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 36 | // a validation key and the location of the third-party responsible for |
| 37 | // discharging the caveat. A discharge for this caveat is a signed assertion whose |
| 38 | // signature can be verified using the validation key. |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 39 | type publicKeyCaveat struct { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 40 | // RandNonce specifies a cryptographically random nonce (of fixed length) that |
| 41 | // uniquely identifies the caveat. |
| 42 | RandNonce []uint8 |
| 43 | |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 44 | // DischargeMintingCaveat specifies the caveat that has to be validated |
| 45 | // before minting a discharge for a publicKeyCaveat. A byte slice containing |
| 46 | // VOM-encoded security.Caveat is used to enable a publicKeyCaveat to be |
| 47 | // validated by devices that cannot decode the discharge minting caveats. |
| 48 | DischargeMintingCaveat []byte |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 49 | |
| 50 | // ValidationKey specifies the public key of the discharging-party. |
| 51 | ValidationKey wire.PublicKey |
| 52 | |
| 53 | // ThirdPartyLocation specifies the global Veyron name of the discharging-party. |
| 54 | ThirdPartyLocation string |
| 55 | } |
| 56 | |
| 57 | // ID returns a unique 32bytes long identity for the caveat based on the random nonce |
| 58 | // and SHA-256 hash of the restriction embedded in the caveat. |
| 59 | // |
| 60 | // TODO(ataly, ashankar): A 256bit hash is probably much stronger that what we need |
| 61 | // here. Can we truncate the hash to 96bits? |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 62 | func (c *publicKeyCaveat) ID() security.ThirdPartyCaveatID { |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 63 | return security.ThirdPartyCaveatID(id(c.RandNonce, c.DischargeMintingCaveat)) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 64 | } |
| 65 | |
| 66 | // Location returns the third-party location embedded in the caveat. |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 67 | func (c *publicKeyCaveat) Location() string { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 68 | return c.ThirdPartyLocation |
| 69 | } |
| 70 | |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 71 | func (c *publicKeyCaveat) String() string { |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 72 | return fmt.Sprintf("publicKeyCaveat{DischargeMintingCaveat: (%v bytes), ThirdPartyLocation: %q}", len(c.DischargeMintingCaveat), c.ThirdPartyLocation) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 73 | } |
| 74 | |
| 75 | // Validate verifies whether the caveat validates for the provided context. |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 76 | func (c *publicKeyCaveat) Validate(ctx security.Context) error { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 77 | // Check whether the context has a dicharge matching the caveat's ID. |
| 78 | dis, ok := ctx.CaveatDischarges()[c.ID()] |
| 79 | if !ok { |
| 80 | return errPublicKeyCaveat(c, fmt.Errorf("missing discharge")) |
| 81 | } |
| 82 | |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 83 | pkDischarge, ok := dis.(*publicKeyDischarge) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 84 | if !ok { |
| 85 | return errPublicKeyCaveat(c, fmt.Errorf("caveat of type %T cannot be validated with discharge of type %T", c, dis)) |
| 86 | } |
| 87 | |
| 88 | // Validate all caveats present on the discharge. |
| 89 | for _, cav := range pkDischarge.Caveats { |
| 90 | if err := cav.Validate(ctx); err != nil { |
| 91 | return errPublicKeyCaveat(c, err) |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | // Check the discharge signature with the validation key from the caveat. |
| 96 | key, err := c.ValidationKey.Decode() |
| 97 | if err != nil { |
| 98 | return errPublicKeyCaveat(c, err) |
| 99 | } |
| 100 | var r, s big.Int |
| 101 | if !ecdsa.Verify(key, pkDischarge.contentHash(), r.SetBytes(pkDischarge.Signature.R), s.SetBytes(pkDischarge.Signature.S)) { |
| 102 | return errPublicKeyCaveat(c, fmt.Errorf("discharge %v has invalid signature", dis)) |
| 103 | } |
| 104 | return nil |
| 105 | } |
| 106 | |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 107 | // publicKeyDischarge implements security.ThirdPartyDischarge. It specifies a |
| 108 | // discharge for a publicKeyCaveat, and includes a signature that can be verified |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 109 | // with the validation key of the caveat. Additionally, the discharge may include |
| 110 | // service caveats which must all be valid in order for the discharge to be |
| 111 | // considered valid. |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 112 | type publicKeyDischarge struct { |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 113 | // ThirdPartyCaveatID is used to match a Discharge with the Caveat it is for. |
| 114 | ThirdPartyCaveatID security.ThirdPartyCaveatID |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 115 | |
| 116 | // Caveats under which this Discharge is valid. |
| 117 | Caveats []wire.Caveat |
| 118 | |
| 119 | // Signature on the contents of the discharge obtained using the private key |
| 120 | // corresponding to the validaton key in the caveat. |
| 121 | Signature wire.Signature |
| 122 | } |
| 123 | |
| 124 | // CaveatID returns a unique identity for the discharge based on the random nonce and |
| 125 | // restriction embedded in the discharge. |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 126 | func (d *publicKeyDischarge) CaveatID() security.ThirdPartyCaveatID { |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 127 | return d.ThirdPartyCaveatID |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 128 | } |
| 129 | |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 130 | func (d *publicKeyDischarge) ThirdPartyCaveats() []security.ServiceCaveat { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 131 | return wire.DecodeThirdPartyCaveats(d.Caveats) |
| 132 | } |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 133 | |
| 134 | // sign uses the provided private key to sign the contents of the discharge. The private |
| 135 | // key typically belongs to the principal that minted the discharge. |
| 136 | func (d *publicKeyDischarge) sign(key *ecdsa.PrivateKey) error { |
| 137 | r, s, err := ecdsa.Sign(rand.Reader, key, d.contentHash()) |
| 138 | if err != nil { |
| 139 | return err |
| 140 | } |
| 141 | d.Signature.R = r.Bytes() |
| 142 | d.Signature.S = s.Bytes() |
| 143 | return nil |
| 144 | } |
| 145 | |
| 146 | func (d *publicKeyDischarge) contentHash() []byte { |
| 147 | h := sha256.New() |
| 148 | tmp := make([]byte, binary.MaxVarintLen64) |
| 149 | |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 150 | wire.WriteBytes(h, tmp, []byte(d.ThirdPartyCaveatID)) |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 151 | for _, cav := range d.Caveats { |
| 152 | wire.WriteString(h, tmp, string(cav.Service)) |
| 153 | wire.WriteBytes(h, tmp, cav.Bytes) |
| 154 | } |
| 155 | return h.Sum(nil) |
| 156 | } |
| 157 | |
| 158 | // NewPublicKeyCaveat returns a new third-party caveat from the provided restriction, |
| 159 | // third-party identity, and third-party location. |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 160 | func NewPublicKeyCaveat(dischargeMintingCaveat security.Caveat, thirdParty security.PublicID, location string) (security.ThirdPartyCaveat, error) { |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 161 | nonce := make([]uint8, nonceLength) |
| 162 | if _, err := rand.Read(nonce); err != nil { |
| 163 | return nil, err |
| 164 | } |
| 165 | |
| 166 | var validationKey wire.PublicKey |
| 167 | if err := validationKey.Encode(thirdParty.PublicKey()); err != nil { |
| 168 | return nil, err |
| 169 | } |
| 170 | |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 171 | var mintingCaveatEncoded bytes.Buffer |
| 172 | vom.NewEncoder(&mintingCaveatEncoded).Encode(dischargeMintingCaveat) |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 173 | return &publicKeyCaveat{ |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 174 | RandNonce: nonce, |
| 175 | DischargeMintingCaveat: mintingCaveatEncoded.Bytes(), |
| 176 | ValidationKey: validationKey, |
| 177 | ThirdPartyLocation: location, |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 178 | }, nil |
| 179 | } |
| 180 | |
| 181 | // NewPublicKeyDischarge returns a new discharge for the provided caveat |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 182 | // after verifying that the caveats for minting a discharge are met. |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 183 | // |
| 184 | // The CaveatID of the discharge is the same as the ID of the caveat, and |
| 185 | // the discharge includes the provided service caveats along with a universal |
| 186 | // expiry caveat for the provided duration. The discharge also includes a |
| 187 | // signature over its contents obtained from the provided private key. |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 188 | func NewPublicKeyDischarge(caveat security.ThirdPartyCaveat, ctx security.Context, dischargingKey *ecdsa.PrivateKey, duration time.Duration, caveats []security.ServiceCaveat) (security.ThirdPartyDischarge, error) { |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 189 | cav, ok := caveat.(*publicKeyCaveat) |
| 190 | if !ok { |
| 191 | return nil, fmt.Errorf("cannot mint discharges for %T", caveat) |
| 192 | } |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 193 | var mintingCaveat security.Caveat |
| 194 | mcBuf := bytes.NewReader(cav.DischargeMintingCaveat) |
| 195 | if err := vom.NewDecoder(mcBuf).Decode(&mintingCaveat); err != nil { |
| 196 | return nil, fmt.Errorf("failed to decode DischargeMintingCaveat: %s", err) |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 197 | } |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 198 | if err := mintingCaveat.Validate(ctx); err != nil { |
| 199 | return nil, fmt.Errorf("failed to validate DischargeMintingCaveat: %s", err) |
| 200 | } |
| 201 | |
| 202 | discharge := &publicKeyDischarge{ThirdPartyCaveatID: caveat.ID()} |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 203 | |
| 204 | now := time.Now() |
| 205 | expiryCaveat := &vcaveat.Expiry{IssueTime: now, ExpiryTime: now.Add(duration)} |
| 206 | caveats = append(caveats, security.UniversalCaveat(expiryCaveat)) |
| 207 | |
| 208 | encodedCaveats, err := wire.EncodeCaveats(caveats) |
| 209 | if err != nil { |
| 210 | return nil, err |
| 211 | } |
| 212 | discharge.Caveats = encodedCaveats |
| 213 | |
| 214 | if err := discharge.sign(dischargingKey); err != nil { |
| 215 | return nil, err |
| 216 | } |
| 217 | |
| 218 | return discharge, nil |
| 219 | } |
| 220 | |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 221 | // id calculates the sha256 hash of length-delimited byte slices |
| 222 | func id(slices ...[]byte) string { |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 223 | h := sha256.New() |
| 224 | tmp := make([]byte, binary.MaxVarintLen64) |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 225 | |
Andres Erbsen | dde52bf | 2014-06-09 09:42:33 -0700 | [diff] [blame^] | 226 | for _, slice := range slices { |
| 227 | wire.WriteBytes(h, tmp, slice) |
| 228 | } |
| 229 | return string(h.Sum(nil)) |
| 230 | } |
| 231 | |
| 232 | func init() { |
| 233 | vom.Register(publicKeyCaveat{}) |
| 234 | vom.Register(publicKeyDischarge{}) |
Asim Shankar | 23c84a0 | 2014-06-04 15:17:10 -0700 | [diff] [blame] | 235 | } |