github.com/google/osv-scalibr@v0.4.1/veles/secrets/privatekey/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 privatekey 16 17 import ( 18 "crypto/ecdsa" 19 "crypto/ed25519" 20 "crypto/rsa" 21 "crypto/x509" 22 "encoding/pem" 23 "regexp" 24 25 "github.com/google/osv-scalibr/veles" 26 ) 27 28 // PEM keys are typically a few KB 29 const maxTokenLength = 1280 * 1024 30 31 // Regex to match PEM/OpenSSH key blocks 32 var blockRe = regexp.MustCompile(`(?s)-----BEGIN (?:OPENSSH|RSA|DSA|EC|ED25519|ENCRYPTED)? ?PRIVATE KEY-----.*?-----END (?:OPENSSH|RSA|DSA|EC|ED25519|ENCRYPTED)? ?PRIVATE KEY-----`) 33 34 var _ veles.Detector = NewDetector() 35 36 // detector is a Veles Detector. 37 type detector struct{} 38 39 // NewDetector returns a Detector that extracts and validates private key blocks. 40 func NewDetector() veles.Detector { 41 return &detector{} 42 } 43 44 func (d *detector) MaxSecretLen() uint32 { 45 return maxTokenLength 46 } 47 48 func (d *detector) Detect(content []byte) ([]veles.Secret, []int) { 49 if len(content) > maxTokenLength { 50 return nil, nil 51 } 52 53 var secrets []veles.Secret 54 var offsets []int 55 56 // 1. PEM detection 57 pemMatches := blockRe.FindAllIndex(content, -1) 58 for _, m := range pemMatches { 59 block := string(content[m[0]:m[1]]) 60 if validatePEMBlock(block) { 61 secrets = append(secrets, PrivateKey{Block: block}) 62 offsets = append(offsets, m[0]) 63 } 64 } 65 66 // 2. DER detection 67 if detectDER(content) { 68 secrets = append(secrets, PrivateKey{Der: content}) 69 offsets = append(offsets, 0) 70 } 71 72 return secrets, offsets 73 } 74 75 // validatePEMBlock runs lightweight structural validation on a private key block. 76 // Returns true if successful, or false otherwise. 77 func validatePEMBlock(block string) bool { 78 p, _ := pem.Decode([]byte(block)) 79 if p == nil { 80 return false 81 } 82 83 switch p.Type { 84 case "RSA PRIVATE KEY": 85 _, err := x509.ParsePKCS1PrivateKey(p.Bytes) 86 return err == nil 87 88 case "EC PRIVATE KEY": 89 _, err := x509.ParseECPrivateKey(p.Bytes) 90 return err == nil 91 92 case "PRIVATE KEY", "ENCRYPTED PRIVATE KEY": 93 _, err := x509.ParsePKCS8PrivateKey(p.Bytes) 94 return err == nil 95 96 case "DSA PRIVATE KEY", "ED25519 PRIVATE KEY", "OPENSSH PRIVATE KEY": 97 // minimal validation, just accept 98 return true 99 100 default: 101 return false 102 } 103 } 104 105 // detectDER tries PKCS#8, PKCS#1, and EC DER encodings. 106 // Returns true if successful, or false otherwise. 107 func detectDER(data []byte) bool { 108 // PKCS#8 wrapper 109 if key, err := x509.ParsePKCS8PrivateKey(data); err == nil { 110 switch key.(type) { 111 case *rsa.PrivateKey: 112 return true 113 case *ecdsa.PrivateKey: 114 return true 115 case ed25519.PrivateKey: 116 return true 117 default: 118 return true 119 } 120 } 121 122 // PKCS#1 RSA 123 if _, err := x509.ParsePKCS1PrivateKey(data); err == nil { 124 return true 125 } 126 127 // Raw EC 128 if _, err := x509.ParseECPrivateKey(data); err == nil { 129 return true 130 } 131 132 return false 133 }