1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
|
package crypto
import (
"bytes"
"errors"
"fmt"
"time"
pgpErrors "github.com/ProtonMail/go-crypto/openpgp/errors"
"github.com/ProtonMail/go-crypto/openpgp/packet"
openpgp "github.com/ProtonMail/go-crypto/openpgp/v2"
"github.com/ProtonMail/gopenpgp/v3/constants"
)
// VerifiedSignature is a result of a signature verification.
type VerifiedSignature struct {
Signature *packet.Signature
SignedBy *Key
SignatureError *SignatureVerificationError
}
// SignatureVerificationError is returned from Decrypt and VerifyDetached
// functions when signature verification fails.
type SignatureVerificationError struct {
Status int
Message string
Cause error
}
// VerifyResult is a result of a pgp message signature verification.
type VerifyResult struct {
// All signatures found in the message.
Signatures []*VerifiedSignature
// The selected signature for the result.
// i.e., the first successfully verified signature in Signatures
// or the last signature Signatures[len(Signatures)-1].
selectedSignature *VerifiedSignature
// The signature error of the selected signature.
// Is nil for a successful verification.
signatureError *SignatureVerificationError
}
// SignatureCreationTime returns the creation time of
// the selected verified signature if found, else returns 0.
func (vr *VerifyResult) SignatureCreationTime() int64 {
if vr.selectedSignature == nil || vr.selectedSignature.Signature == nil {
return 0
}
return vr.selectedSignature.Signature.CreationTime.Unix()
}
// SignedWithType returns the type of the signature if found, else returns 0.
// Not supported in go-mobile use SignedWithTypeInteger instead.
func (vr *VerifyResult) SignedWithType() packet.SignatureType {
if vr.selectedSignature == nil || vr.selectedSignature.Signature == nil {
return 0
}
return vr.selectedSignature.Signature.SigType
}
// SignedWithTypeInt8 returns the type of the signature as int8 type if found, else returns 0.
// See constants.SigType... for the different types.
func (vr *VerifyResult) SignedWithTypeInt8() int8 {
return int8(vr.SignedWithType())
}
// SignedByKeyId returns the key id of the key that was used to verify the selected signature,
// if found, else returns 0.
// Not supported in go-mobile use SignedByKeyIdString instead.
func (vr *VerifyResult) SignedByKeyId() uint64 {
if vr.selectedSignature == nil || vr.selectedSignature.Signature == nil {
return 0
}
return *vr.selectedSignature.Signature.IssuerKeyId
}
// SignedByKeyIdHex returns the key id of the key that was used to verify the selected signature
// as a hex encoded string.
// Helper for go-mobile.
func (vr *VerifyResult) SignedByKeyIdHex() string {
return keyIDToHex(vr.SignedByKeyId())
}
// SignedByFingerprint returns the key fingerprint of the key that was used to verify the selected signature,
// if found, else returns nil.
func (vr *VerifyResult) SignedByFingerprint() []byte {
if vr.selectedSignature == nil || vr.selectedSignature.Signature == nil {
return nil
}
if vr.selectedSignature.Signature.IssuerFingerprint != nil {
return vr.selectedSignature.Signature.IssuerFingerprint
}
if vr.selectedSignature.SignedBy != nil {
return vr.selectedSignature.SignedBy.GetFingerprintBytes()
}
return nil
}
// SignedByKey returns the key that was used to verify the selected signature,
// if found, else returns nil.
func (vr *VerifyResult) SignedByKey() *Key {
if vr.selectedSignature == nil || vr.selectedSignature.Signature == nil {
return nil
}
key := vr.selectedSignature.SignedBy
if key == nil {
return nil
}
return &Key{
entity: key.entity,
}
}
// Signature returns the serialized openpgp signature packet of the selected signature.
func (vr *VerifyResult) Signature() ([]byte, error) {
if vr.selectedSignature == nil || vr.selectedSignature.Signature == nil {
return nil, errors.New("gopenpgp: no signature present")
}
var serializedSignature bytes.Buffer
if err := vr.selectedSignature.Signature.Serialize(&serializedSignature); err != nil {
return nil, fmt.Errorf("gopenpgp: signature serialization failed: %w", err)
}
return serializedSignature.Bytes(), nil
}
// SignatureError returns nil if no signature err occurred else
// the signature error.
func (vr *VerifyResult) SignatureError() error {
if vr == nil || vr.signatureError == nil {
return nil
}
return *vr.signatureError
}
// SignatureErrorExplicit returns nil if no signature err occurred else
// the explicit signature error.
func (vr *VerifyResult) SignatureErrorExplicit() *SignatureVerificationError {
return vr.signatureError
}
// ConstrainToTimeRange updates the signature result to only consider
// signatures with a creation time within the given time frame.
// unixFrom and unixTo are in unix time and are inclusive.
func (vr *VerifyResult) ConstrainToTimeRange(unixFrom int64, unixTo int64) {
for _, signature := range vr.Signatures {
if signature.Signature != nil && signature.SignatureError == nil {
sigUnixTime := signature.Signature.CreationTime.Unix()
if sigUnixTime < unixFrom || sigUnixTime > unixTo {
sigError := newSignatureFailed(errors.New("gopenpgp: signature creation time is out of range"))
signature.SignatureError = &sigError
}
}
}
// Reselect
vr.selectSignature()
}
// Error is the base method for all errors.
func (e SignatureVerificationError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("Signature Verification Error: %v caused by %v", e.Message, e.Cause)
}
return fmt.Sprintf("Signature Verification Error: %v", e.Message)
}
// Unwrap returns the cause of failure.
func (e SignatureVerificationError) Unwrap() error {
return e.Cause
}
// ------------------
// Internal functions
// ------------------
// selectSignature selects the main signature to show in the result.
// Selection policy:
// first successfully verified or
// last signature with an error and a matching key or
// last signature with an error if no key matched.
func (vr *VerifyResult) selectSignature() {
var keyMatch bool
for _, signature := range vr.Signatures {
if signature.SignedBy != nil {
keyMatch = true
vr.selectedSignature = signature
vr.signatureError = signature.SignatureError
if signature.SignatureError == nil {
break
}
}
}
if !keyMatch && len(vr.Signatures) > 0 {
signature := vr.Signatures[len(vr.Signatures)-1]
vr.selectedSignature = signature
vr.signatureError = signature.SignatureError
}
}
// newSignatureFailed creates a new SignatureVerificationError, type
// SignatureFailed.
func newSignatureBadContext(cause error) SignatureVerificationError {
return SignatureVerificationError{
Status: constants.SIGNATURE_BAD_CONTEXT,
Message: "Invalid signature context",
Cause: cause,
}
}
func newSignatureFailed(cause error) SignatureVerificationError {
return SignatureVerificationError{
Status: constants.SIGNATURE_FAILED,
Message: "Invalid signature",
Cause: cause,
}
}
// newSignatureNotSigned creates a new SignatureVerificationError, type
// SignatureNotSigned.
func newSignatureNotSigned() SignatureVerificationError {
return SignatureVerificationError{
Status: constants.SIGNATURE_NOT_SIGNED,
Message: "Missing signature",
}
}
// newSignatureNoVerifier creates a new SignatureVerificationError, type
// SignatureNoVerifier.
func newSignatureNoVerifier() SignatureVerificationError {
return SignatureVerificationError{
Status: constants.SIGNATURE_NO_VERIFIER,
Message: "No matching signature",
}
}
// processSignatureExpiration handles signature time verification manually, so
// we can ignore signature expired errors if configured so.
func processSignatureExpiration(sig *packet.Signature, toCheck error, verifyTime int64, disableTimeCheck bool) error {
if sig == nil || !errors.Is(toCheck, pgpErrors.ErrSignatureExpired) {
return toCheck
}
if disableTimeCheck || verifyTime == 0 {
return nil
}
return toCheck
}
func createVerifyResult(
md *openpgp.MessageDetails,
verifierKey *KeyRing,
verificationContext *VerificationContext,
verifyTime int64,
disableTimeCheck bool,
) (*VerifyResult, error) {
if !md.IsSigned {
signatureError := newSignatureNotSigned()
return &VerifyResult{
signatureError: &signatureError,
}, nil
}
if !md.IsVerified {
return nil, errors.New("gopenpgp: message has not been verified")
}
verifiedSignatures := make([]*VerifiedSignature, len(md.SignatureCandidates))
for candidateIndex, signature := range md.SignatureCandidates {
var singedBy *Key
if signature.SignedBy != nil {
singedBy = &Key{
entity: signature.SignedBy.Entity,
}
}
verifiedSignature := &VerifiedSignature{
Signature: signature.CorrespondingSig,
SignedBy: singedBy,
}
signature.SignatureError = processSignatureExpiration(
signature.CorrespondingSig,
signature.SignatureError,
verifyTime,
disableTimeCheck,
)
var signatureError SignatureVerificationError
switch {
case verifierKey == nil || len(verifierKey.entities) == 0 ||
errors.Is(signature.SignatureError, pgpErrors.ErrUnknownIssuer):
signatureError = newSignatureNoVerifier()
case signature.SignatureError != nil:
signatureError = newSignatureFailed(signature.SignatureError)
case verificationContext != nil:
err := verificationContext.verifyContext(signature.CorrespondingSig)
if err != nil {
signatureError = newSignatureBadContext(err)
}
}
if signatureError.Status != constants.SIGNATURE_OK {
verifiedSignature.SignatureError = &signatureError
}
verifiedSignatures[candidateIndex] = verifiedSignature
}
verifyResult := &VerifyResult{
Signatures: verifiedSignatures,
}
// Select the signature to show in the result
verifyResult.selectSignature()
return verifyResult, nil
}
// SigningContext gives the context that will be
// included in the signature's notation data.
type SigningContext struct {
Value string
IsCritical bool
}
// NewSigningContext creates a new signing context.
// The value is set to the notation data.
// isCritical controls whether the notation is flagged as a critical packet.
func NewSigningContext(value string, isCritical bool) *SigningContext {
return &SigningContext{Value: value, IsCritical: isCritical}
}
func (context *SigningContext) getNotation() *packet.Notation {
return &packet.Notation{
Name: constants.SignatureContextName,
Value: []byte(context.Value),
IsCritical: context.IsCritical,
IsHumanReadable: true,
}
}
// VerificationContext gives the context that will be
// used to verify the signature.
type VerificationContext struct {
Value string
IsRequired bool
RequiredAfter int64
}
// NewVerificationContext creates a new verification context.
// The value is checked against the signature's notation data.
// If isRequired is false, the signature is allowed to have no context set.
// If requiredAfter is != 0, the signature is allowed to have no context set if it
// was created before the unix time set in requiredAfter.
func NewVerificationContext(value string, isRequired bool, requiredAfter int64) *VerificationContext {
return &VerificationContext{
Value: value,
IsRequired: isRequired,
RequiredAfter: requiredAfter,
}
}
func (context *VerificationContext) isRequiredAtTime(signatureTime time.Time) bool {
return context.IsRequired &&
(context.RequiredAfter == 0 || signatureTime.After(time.Unix(context.RequiredAfter, 0)))
}
func findContext(notations []*packet.Notation) (string, error) {
context := ""
for _, notation := range notations {
if notation.Name == constants.SignatureContextName {
if context != "" {
return "", errors.New("gopenpgp: signature has multiple context notations")
}
if !notation.IsHumanReadable {
return "", errors.New("gopenpgp: context notation was not set as human-readable")
}
context = string(notation.Value)
}
}
return context, nil
}
func (context *VerificationContext) verifyContext(sig *packet.Signature) error {
if sig == nil {
return errors.New("gopenpgp: no signature packet found for signature")
}
signatureContext, err := findContext(sig.Notations)
if err != nil {
return err
}
if signatureContext != context.Value {
contextRequired := context.isRequiredAtTime(sig.CreationTime)
if contextRequired {
return errors.New("gopenpgp: signature did not have the required context")
} else if signatureContext != "" {
return errors.New("gopenpgp: signature had a wrong context")
}
}
return nil
}
|