github.com/google/osv-scalibr@v0.4.1/veles/secrets/vapid/detector.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package vapid 16 17 import ( 18 "crypto/ecdh" 19 "encoding/base64" 20 "fmt" 21 "regexp" 22 23 "github.com/google/osv-scalibr/veles" 24 "github.com/google/osv-scalibr/veles/secrets/common/pair" 25 ) 26 27 const ( 28 publicKeyLen = 87 29 privateKeyLen = 43 30 maxKeyLen = max(publicKeyLen, privateKeyLen) 31 32 // maxDistance is the maximum window length to pair env-style credentials. 33 maxDistance = 10 * 1 << 10 // 10 KiB 34 ) 35 36 var ( 37 // match a base64 blob of exactly 87 characters 38 publicKeyPattern = regexp.MustCompile(`\b[A-Za-z0-9_-]{87}\b`) 39 // match a base64 blob of exactly 43 characters 40 privateKeyPattern = regexp.MustCompile(`\b[A-Za-z0-9_-]{43}\b`) 41 ) 42 43 // NewDetector returns a VAPID private key detector 44 // 45 // a key is detected if: 46 // 47 // - it has some context, (ex: `VAPID_KEY:base64blob`) 48 // - it is validated against a nearby public key 49 func NewDetector() veles.Detector { 50 return &pair.Detector{ 51 MaxElementLen: maxKeyLen, MaxDistance: maxDistance, 52 FindA: pair.FindAllMatches(publicKeyPattern), 53 FindB: pair.FindAllMatches(privateKeyPattern), 54 FromPair: func(p pair.Pair) (veles.Secret, bool) { 55 pubB64, privB64 := string(p.A.Value), string(p.B.Value) 56 if ok, _ := validateVAPIDKeys(pubB64, privB64); !ok { 57 return nil, false 58 } 59 return Key{PublicB64: pubB64, PrivateB64: privB64}, true 60 }, 61 } 62 } 63 64 // validateVAPIDKeys checks if a VAPID public key matches a private key (P-256) 65 func validateVAPIDKeys(pubB64, privB64 string) (bool, error) { 66 // Decode base64url keys 67 pubBytes, err := base64.RawURLEncoding.DecodeString(pubB64) 68 if err != nil { 69 return false, fmt.Errorf("invalid public key: %w", err) 70 } 71 privBytes, err := base64.RawURLEncoding.DecodeString(privB64) 72 if err != nil { 73 return false, fmt.Errorf("invalid private key: %w", err) 74 } 75 76 // Load curve 77 curve := ecdh.P256() 78 79 // Parse keys 80 pubKey, err := curve.NewPublicKey(pubBytes) 81 if err != nil { 82 return false, fmt.Errorf("failed to parse public key: %w", err) 83 } 84 85 privKey, err := curve.NewPrivateKey(privBytes) 86 if err != nil { 87 return false, fmt.Errorf("failed to parse private key: %w", err) 88 } 89 90 // Compare public keys 91 expectedPub := privKey.PublicKey() 92 if !expectedPub.Equal(pubKey) { 93 return false, nil 94 } 95 96 return true, nil 97 }