github.com/psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/accesscontrol/accesscontrol.go (about) 1 /* 2 * Copyright (c) 2018, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 // Package accesscontrol implements an access control authorization scheme 21 // based on digital signatures. 22 // 23 // Authorizations for specified access types are issued by an entity that 24 // digitally signs each authorization. The digital signature is verified 25 // by service providers before granting the specified access type. Each 26 // authorization includes an expiry date and a unique ID that may be used 27 // to mitigate malicious reuse/sharing of authorizations. 28 // 29 // In a typical deployment, the signing keys will be present on issuing 30 // entities which are distinct from service providers. Only verification 31 // keys will be deployed to service providers. 32 // 33 // An authorization is represented in JSON, which is then base64-encoded 34 // for transport: 35 // 36 // { 37 // "Authorization" : { 38 // "ID" : <derived unique ID>, 39 // "AccessType" : <access type name; e.g., "my-access">, 40 // "Expires" : <RFC3339-encoded UTC time value> 41 // }, 42 // "SigningKeyID" : <unique key ID>, 43 // "Signature" : <Ed25519 digital signature> 44 // } 45 // 46 package accesscontrol 47 48 import ( 49 "crypto/ed25519" 50 "crypto/rand" 51 "crypto/sha256" 52 "crypto/subtle" 53 "encoding/base64" 54 "encoding/json" 55 "io" 56 "time" 57 58 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 59 "golang.org/x/crypto/hkdf" 60 ) 61 62 const ( 63 keyIDLength = 32 64 authorizationIDKeyLength = 32 65 authorizationIDLength = 32 66 ) 67 68 // SigningKey is the private key used to sign newly issued 69 // authorizations for the specified access type. The key ID 70 // is included in authorizations and identifies the 71 // corresponding verification keys. 72 // 73 // AuthorizationIDKey is used to produce a unique 74 // authentication ID that cannot be mapped back to its seed 75 // value. 76 type SigningKey struct { 77 ID []byte 78 AccessType string 79 AuthorizationIDKey []byte 80 PrivateKey []byte 81 } 82 83 // VerificationKey is the public key used to verify signed 84 // authentications issued for the specified access type. The 85 // authorization references the expected public key by ID. 86 type VerificationKey struct { 87 ID []byte 88 AccessType string 89 PublicKey []byte 90 } 91 92 // NewKeyPair generates a new authorization signing key pair. 93 func NewKeyPair( 94 accessType string) (*SigningKey, *VerificationKey, error) { 95 96 ID := make([]byte, keyIDLength) 97 _, err := rand.Read(ID) 98 if err != nil { 99 return nil, nil, errors.Trace(err) 100 } 101 102 authorizationIDKey := make([]byte, authorizationIDKeyLength) 103 _, err = rand.Read(authorizationIDKey) 104 if err != nil { 105 return nil, nil, errors.Trace(err) 106 } 107 108 publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) 109 if err != nil { 110 return nil, nil, errors.Trace(err) 111 } 112 113 signingKey := &SigningKey{ 114 ID: ID, 115 AccessType: accessType, 116 AuthorizationIDKey: authorizationIDKey, 117 PrivateKey: privateKey, 118 } 119 120 verificationKey := &VerificationKey{ 121 ID: ID, 122 AccessType: accessType, 123 PublicKey: publicKey, 124 } 125 126 return signingKey, verificationKey, nil 127 } 128 129 // Authorization describes an authorization, with a unique ID, 130 // granting access to a specified access type, and expiring at 131 // the specified time. 132 // 133 // An Authorization is embedded within a digitally signed 134 // object. This wrapping object adds a signature and a signing 135 // key ID. 136 type Authorization struct { 137 ID []byte 138 AccessType string 139 Expires time.Time 140 } 141 142 type signedAuthorization struct { 143 Authorization json.RawMessage 144 SigningKeyID []byte 145 Signature []byte 146 } 147 148 // ValidateSigningKey checks that a signing key is correctly configured. 149 func ValidateSigningKey(signingKey *SigningKey) error { 150 if len(signingKey.ID) != keyIDLength || 151 len(signingKey.AccessType) < 1 || 152 len(signingKey.AuthorizationIDKey) != authorizationIDKeyLength || 153 len(signingKey.PrivateKey) != ed25519.PrivateKeySize { 154 return errors.TraceNew("invalid signing key") 155 } 156 return nil 157 } 158 159 // IssueAuthorization issues an authorization signed with the 160 // specified signing key. 161 // 162 // seedAuthorizationID should be a value that uniquely identifies 163 // the purchase, subscription, or transaction that backs the 164 // authorization; a distinct unique authorization ID will be derived 165 // from the seed without revealing the original value. The authorization 166 // ID is to be used to mitigate malicious authorization reuse/sharing. 167 // 168 // The first return value is a base64-encoded, serialized JSON representation 169 // of the signed authorization that can be passed to VerifyAuthorization. The 170 // second return value is the unique ID of the signed authorization returned in 171 // the first value. 172 func IssueAuthorization( 173 signingKey *SigningKey, 174 seedAuthorizationID []byte, 175 expires time.Time) (string, []byte, error) { 176 177 err := ValidateSigningKey(signingKey) 178 if err != nil { 179 return "", nil, errors.Trace(err) 180 } 181 182 hkdf := hkdf.New(sha256.New, signingKey.AuthorizationIDKey, nil, seedAuthorizationID) 183 ID := make([]byte, authorizationIDLength) 184 _, err = io.ReadFull(hkdf, ID) 185 if err != nil { 186 return "", nil, errors.Trace(err) 187 } 188 189 auth := Authorization{ 190 ID: ID, 191 AccessType: signingKey.AccessType, 192 Expires: expires.UTC(), 193 } 194 195 authJSON, err := json.Marshal(auth) 196 if err != nil { 197 return "", nil, errors.Trace(err) 198 } 199 200 signature := ed25519.Sign(signingKey.PrivateKey, authJSON) 201 202 signedAuth := signedAuthorization{ 203 Authorization: authJSON, 204 SigningKeyID: signingKey.ID, 205 Signature: signature, 206 } 207 208 signedAuthJSON, err := json.Marshal(signedAuth) 209 if err != nil { 210 return "", nil, errors.Trace(err) 211 } 212 213 encodedSignedAuth := base64.StdEncoding.EncodeToString(signedAuthJSON) 214 215 return encodedSignedAuth, ID, nil 216 } 217 218 // VerificationKeyRing is a set of verification keys to be deployed 219 // to a service provider for verifying access authorizations. 220 type VerificationKeyRing struct { 221 Keys []*VerificationKey 222 } 223 224 // ValidateVerificationKeyRing checks that a verification key ring is 225 // correctly configured. 226 func ValidateVerificationKeyRing(keyRing *VerificationKeyRing) error { 227 for _, key := range keyRing.Keys { 228 if len(key.ID) != keyIDLength || 229 len(key.AccessType) < 1 || 230 len(key.PublicKey) != ed25519.PublicKeySize { 231 return errors.TraceNew("invalid verification key") 232 } 233 } 234 return nil 235 } 236 237 // VerifyAuthorization verifies the signed authorization and, when 238 // verified, returns the embedded Authorization struct with the 239 // access control information. 240 // 241 // The key ID in the signed authorization is used to select the 242 // appropriate verification key from the key ring. 243 func VerifyAuthorization( 244 keyRing *VerificationKeyRing, 245 encodedSignedAuthorization string) (*Authorization, error) { 246 247 err := ValidateVerificationKeyRing(keyRing) 248 if err != nil { 249 return nil, errors.Trace(err) 250 } 251 252 signedAuthorizationJSON, err := base64.StdEncoding.DecodeString( 253 encodedSignedAuthorization) 254 if err != nil { 255 return nil, errors.Trace(err) 256 } 257 258 var signedAuth signedAuthorization 259 err = json.Unmarshal(signedAuthorizationJSON, &signedAuth) 260 if err != nil { 261 return nil, errors.Trace(err) 262 } 263 264 if len(signedAuth.SigningKeyID) != keyIDLength { 265 return nil, errors.TraceNew("invalid key ID length") 266 } 267 268 if len(signedAuth.Signature) != ed25519.SignatureSize { 269 return nil, errors.TraceNew("invalid signature length") 270 } 271 272 var verificationKey *VerificationKey 273 274 for _, key := range keyRing.Keys { 275 if subtle.ConstantTimeCompare(signedAuth.SigningKeyID, key.ID) == 1 { 276 verificationKey = key 277 } 278 } 279 280 if verificationKey == nil { 281 return nil, errors.TraceNew("invalid key ID") 282 } 283 284 if !ed25519.Verify( 285 verificationKey.PublicKey, signedAuth.Authorization, signedAuth.Signature) { 286 return nil, errors.TraceNew("invalid signature") 287 } 288 289 var auth Authorization 290 291 err = json.Unmarshal(signedAuth.Authorization, &auth) 292 if err != nil { 293 return nil, errors.Trace(err) 294 } 295 296 if len(auth.ID) == 0 { 297 return nil, errors.TraceNew("invalid authorization ID") 298 } 299 300 if auth.AccessType != verificationKey.AccessType { 301 return nil, errors.TraceNew("invalid access type") 302 } 303 304 if auth.Expires.IsZero() { 305 return nil, errors.TraceNew("invalid expiry") 306 } 307 308 if auth.Expires.Before(time.Now().UTC()) { 309 return nil, errors.TraceNew("expired authorization") 310 } 311 312 return &auth, nil 313 }