github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/stream_classifier.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package libkb 5 6 import ( 7 "bufio" 8 "bytes" 9 "encoding/base64" 10 "io" 11 "strings" 12 13 "github.com/keybase/go-codec/codec" 14 "github.com/keybase/saltpack" 15 ) 16 17 // CryptoMessageFormat is one of the known crypto message formats that we admit 18 type CryptoMessageFormat string 19 20 const ( 21 // CryptoMessageFormatPGP is for PGP 22 CryptoMessageFormatPGP CryptoMessageFormat = "pgp" 23 // CryptoMessageFormatKeybaseV0 is for the zeroth version of Keybase signatures, which 24 // will eventually be deprecated. 25 CryptoMessageFormatKeybaseV0 CryptoMessageFormat = "kbv0" 26 // CryptoMessageFormatSaltpack is the Saltpack messaging format for encrypted and signed 27 // messages 28 CryptoMessageFormatSaltpack CryptoMessageFormat = "saltpack" 29 ) 30 31 // CryptoMessageType says what type of crypto message it is, regardless of Format 32 type CryptoMessageType int 33 34 const ( 35 // CryptoMessageTypeEncryption is for an encrypted message 36 CryptoMessageTypeEncryption CryptoMessageType = 0 37 // CryptoMessageTypeAttachedSignature is for an attached signature 38 CryptoMessageTypeAttachedSignature CryptoMessageType = 1 39 // CryptoMessageTypeDetachedSignature is for a detached signature 40 CryptoMessageTypeDetachedSignature CryptoMessageType = 2 41 // CryptoMessageTypeClearSignature is for PGP clearsigning 42 CryptoMessageTypeClearSignature CryptoMessageType = 3 43 // CryptoMessageTypeAmbiguous is for an ambiguous message based on the stream prefix 44 CryptoMessageTypeAmbiguous CryptoMessageType = 4 45 // CryptoMessageTypeSignature is for a sig that can be either attached or detached 46 CryptoMessageTypeSignature CryptoMessageType = 5 47 ) 48 49 // StreamClassification tells what Format the stream is, if it's a Public signature or a Private 50 // Message, if it's a detached or attached signature in the public case, and if it's 51 // armored or binary. 52 type StreamClassification struct { 53 Format CryptoMessageFormat 54 Type CryptoMessageType 55 Armored bool 56 } 57 58 func isBase64KeybaseV0Sig(s string) bool { 59 firstKey := "body" 60 dataBytes := len(firstKey) + 1 61 b64dataBytes := (dataBytes + 1) * 4 / 3 62 if len(s) < b64dataBytes { 63 return false 64 } 65 buf, err := base64.StdEncoding.DecodeString(s[0:b64dataBytes]) 66 if err != nil { 67 return false 68 } 69 // If there is a way to feed base64-like data thats longer than that 70 // b64dataBytes but yields an empty buffer, bail out and not crash later. 71 if len(buf) == 0 { 72 return false 73 } 74 // Packet should be an encoded dictionary of 3 values 75 if buf[0] != 0x83 { 76 return false 77 } 78 var mh codec.MsgpackHandle 79 var encoded []byte 80 _ = codec.NewEncoderBytes(&encoded, &mh).Encode(firstKey) 81 return bytes.HasPrefix(buf[1:], encoded) 82 } 83 84 func isSaltpackMessage(stream *bufio.Reader, sc *StreamClassification) bool { 85 isArmored, _, messageType, _, err := saltpack.ClassifyStream(stream) 86 if err != nil { 87 return false 88 } 89 90 sc.Armored = isArmored 91 92 switch messageType { 93 case saltpack.MessageTypeEncryption, saltpack.MessageTypeSigncryption: 94 sc.Type = CryptoMessageTypeEncryption 95 case saltpack.MessageTypeAttachedSignature: 96 sc.Type = CryptoMessageTypeAttachedSignature 97 case saltpack.MessageTypeDetachedSignature: 98 sc.Type = CryptoMessageTypeDetachedSignature 99 default: 100 return false 101 } 102 sc.Format = CryptoMessageFormatSaltpack 103 return true 104 } 105 106 func isPGPBinary(b []byte, sc *StreamClassification) bool { 107 if len(b) < 2 { 108 return false 109 } 110 // Top bit is set on PGP packets 111 if b[0]&0x80 == 0 { 112 return false 113 } 114 115 var tag byte 116 if (b[0] & 0x40) == 0 { 117 // "Old"-style Tag Format 118 tag = (b[0] & 0x3f) >> 2 119 } else { 120 // "New"-style Tag Format 121 tag = (b[0] & 0x3f) 122 } 123 switch tag { 124 case 0x1: 125 // Encrypted session Key 126 sc.Type = CryptoMessageTypeEncryption 127 case 0x2: 128 // Detached signature 129 sc.Type = CryptoMessageTypeDetachedSignature 130 case 0x4, 0x8: 131 // Either a compressed message or just a one-pass signature type. In either case, 132 // it's likely a signature. 133 sc.Type = CryptoMessageTypeAttachedSignature 134 default: 135 return false 136 } 137 sc.Format = CryptoMessageFormatPGP 138 return true 139 } 140 141 func isUTF16Mark(b []byte) bool { 142 if len(b) < 2 { 143 return false 144 } 145 return ((b[0] == 0xFE && b[1] == 0xFF) || (b[0] == 0xFF && b[1] == 0xFE)) 146 } 147 148 // ClassifyStream takes a stream reader in, and returns a likely classification 149 // of that stream without consuming any data from it. It returns a reader that you 150 // should read from instead, in addition to the classification. If classification 151 // fails, there will be a `UnknownStreamError`, or additional EOF errors if the 152 // stream ended before classification could go. 153 func ClassifyStream(r io.Reader) (sc StreamClassification, out io.Reader, err error) { 154 // 4096 is currently the default buffer size. It is specified explicitly because go 1.9 does not 155 // expose the size of a bufio.Reader (go 1.10 does). 156 stream := bufio.NewReaderSize(r, 4096) 157 158 buf, err := stream.Peek(4096) 159 if err != nil { 160 // If we had a short peek (for example, due to a short stream), we can still continue and ignore the error 161 if len(buf) == 0 { 162 return sc, stream, err 163 } 164 err = nil 165 } 166 167 sb := string(buf) 168 switch { 169 case strings.HasPrefix(sb, "-----BEGIN PGP MESSAGE-----"): 170 sc.Format = CryptoMessageFormatPGP 171 sc.Armored = true 172 sc.Type = CryptoMessageTypeAmbiguous 173 case strings.HasPrefix(sb, "-----BEGIN PGP SIGNATURE-----"): 174 sc.Format = CryptoMessageFormatPGP 175 sc.Armored = true 176 sc.Type = CryptoMessageTypeDetachedSignature 177 case strings.HasPrefix(sb, "-----BEGIN PGP SIGNED MESSAGE-----"): 178 sc.Format = CryptoMessageFormatPGP 179 sc.Armored = true 180 sc.Type = CryptoMessageTypeClearSignature 181 case isSaltpackMessage(stream, &sc): 182 // Format etc. set by isSaltpackBinary(). 183 case isBase64KeybaseV0Sig(sb): 184 sc.Format = CryptoMessageFormatKeybaseV0 185 sc.Armored = true 186 sc.Type = CryptoMessageTypeAttachedSignature 187 case isPGPBinary(buf, &sc): 188 // Format etc. set by isPGPBinary(). 189 case isUTF16Mark(buf): 190 err = UTF16UnsupportedError{} 191 default: 192 err = UnknownStreamError{} 193 } 194 return sc, stream, err 195 }