2018-11-16 12:19:38 +00:00
package protocol
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
2018-12-21 13:43:59 +00:00
"crypto/rsa"
2018-11-16 12:19:38 +00:00
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"io/ioutil"
"net/http"
"time"
2022-10-22 10:03:53 +00:00
asn "git.ma-al.com/goc_marek/go_S-MIME/asn1"
oid "git.ma-al.com/goc_marek/go_S-MIME/oid"
2018-11-16 12:19:38 +00:00
)
// SignedDataContent returns SignedData if ContentType is SignedData.
func ( ci ContentInfo ) SignedDataContent ( ) ( * SignedData , error ) {
if ! ci . ContentType . Equal ( oid . SignedData ) {
return nil , ErrWrongType
}
sd := new ( SignedData )
if rest , err := asn . Unmarshal ( ci . Content . Bytes , sd ) ; err != nil {
return nil , err
} else if len ( rest ) > 0 {
return nil , ErrTrailingData
}
return sd , nil
}
2022-10-22 10:03:53 +00:00
// SignedData ::= SEQUENCE {
// version CMSVersion,
// digestAlgorithms DigestAlgorithmIdentifiers,
// encapContentInfo EncapsulatedContentInfo,
// certificates [0] IMPLICIT CertificateSet OPTIONAL,
// crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
// signerInfos SignerInfos }
2018-11-16 12:19:38 +00:00
type SignedData struct {
Version int ` ` // CMSVersion ::= INTEGER { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) }
DigestAlgorithms [ ] pkix . AlgorithmIdentifier ` asn1:"set" ` //DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier //DigestAlgorithmIdentifier ::= AlgorithmIdentifier
EncapContentInfo EncapsulatedContentInfo ` ` //
Certificates [ ] asn1 . RawValue ` asn1:"optional,set,tag:0" ` // CertificateSet ::= SET OF CertificateChoices
CRLs [ ] RevocationInfoChoice ` asn1:"optional,set,tag:1" ` // RevocationInfoChoices ::= SET OF RevocationInfoChoice
SignerInfos [ ] SignerInfo ` asn1:"set" ` // SignerInfos ::= SET OF SignerInfo
}
2022-10-22 10:03:53 +00:00
// CertificateChoices ::= CHOICE {
// certificate Certificate,
// extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
// v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
// v2AttrCert [2] IMPLICIT AttributeCertificateV2,
// other [3] IMPLICIT OtherCertificateFormat }
2018-11-16 12:19:38 +00:00
type CertificateChoices struct {
Cert x509 . Certificate ` asn1:"optional" `
V2AttrCert asn1 . RawValue ` asn1:"optional,tag:2" `
Other OtherCertificateFormat ` asn1:"optional,tag:3" `
}
2022-10-22 10:03:53 +00:00
// OtherCertificateFormat ::= SEQUENCE {
// otherCertFormat OBJECT IDENTIFIER,
// otherCert ANY DEFINED BY otherCertFormat }
2018-11-16 12:19:38 +00:00
type OtherCertificateFormat struct {
OtherCertFormat asn1 . ObjectIdentifier
OtherCert asn1 . RawValue
}
2022-10-22 10:03:53 +00:00
// RevocationInfoChoice ::= CHOICE {
// crl CertificateList,
// other [1] IMPLICIT OtherRevocationInfoFormat }
2018-11-16 12:19:38 +00:00
type RevocationInfoChoice struct {
Crl pkix . CertificateList ` asn1:"optional" `
Other OtherRevocationInfoFormat ` asn1:"optional,tag:1" `
}
2022-10-22 10:03:53 +00:00
// OtherRevocationInfoFormat ::= SEQUENCE {
// otherRevInfoFormat OBJECT IDENTIFIER,
// otherRevInfo ANY DEFINED BY otherRevInfoFormat }
2018-11-16 12:19:38 +00:00
type OtherRevocationInfoFormat struct {
OtherRevInfoFormat asn1 . ObjectIdentifier
OtherRevInfo asn1 . RawValue
}
// NewSignedData creates a new SignedData.
func NewSignedData ( eci EncapsulatedContentInfo ) ( * SignedData , error ) {
// The version is picked based on which CMS features are used. We only use
// version 1 features, except for supporting non-data econtent.
version := 1
if ! eci . IsTypeData ( ) {
version = 3
}
return & SignedData {
Version : version ,
DigestAlgorithms : [ ] pkix . AlgorithmIdentifier { } ,
EncapContentInfo : eci ,
SignerInfos : [ ] SignerInfo { } ,
} , nil
}
// AddSignerInfo adds a SignerInfo to the SignedData.
2018-12-21 13:43:59 +00:00
func ( sd * SignedData ) AddSignerInfo ( keypPair tls . Certificate , attrs [ ] Attribute ) ( err error ) {
2018-11-16 12:19:38 +00:00
for _ , cert := range keypPair . Certificate {
if err = sd . AddCertificate ( cert ) ; err != nil {
return
}
}
signer := keypPair . PrivateKey . ( crypto . Signer )
cert := keypPair . Leaf
ias , err := NewIssuerAndSerialNumber ( cert )
if err != nil {
return err
}
sid := SignerIdentifier { ias , nil }
2018-12-21 13:43:59 +00:00
var signerOpts crypto . SignerOpts
2018-11-16 12:19:38 +00:00
digestAlgorithm := digestAlgorithmForPublicKey ( cert . PublicKey )
signatureAlgorithm , ok := oid . PublicKeyAlgorithmToSignatureAlgorithm [ keypPair . Leaf . PublicKeyAlgorithm ]
2018-12-21 13:43:59 +00:00
if isRSAPSS ( cert ) {
h := oid . DigestAlgorithmToHash [ digestAlgorithm . Algorithm . String ( ) ]
signatureAlgorithm , signerOpts , err = newPSS ( h , cert . PublicKey . ( * rsa . PublicKey ) )
}
2018-11-16 12:19:38 +00:00
if ! ok {
return errors . New ( "unsupported certificate public key algorithm" )
}
si := SignerInfo {
Version : 1 ,
SID : sid ,
DigestAlgorithm : digestAlgorithm ,
SignedAttrs : nil ,
SignatureAlgorithm : signatureAlgorithm ,
Signature : nil ,
UnsignedAttrs : nil ,
}
// Get the message
content := sd . EncapContentInfo . EContent
if err != nil {
return err
}
if content == nil {
return errors . New ( "already detached" )
}
// Digest the message.
hash , err := si . Hash ( )
if err != nil {
return err
}
2018-12-21 13:43:59 +00:00
if ! isRSAPSS ( cert ) {
signerOpts = hash
}
2018-11-16 12:19:38 +00:00
md := hash . New ( )
if _ , err = md . Write ( content ) ; err != nil {
return err
}
// Build our SignedAttributes
mdAttr , err := NewAttribute ( oid . AttributeMessageDigest , md . Sum ( nil ) )
if err != nil {
return err
}
ctAttr , err := NewAttribute ( oid . AttributeContentType , sd . EncapContentInfo . EContentType )
if err != nil {
return err
}
sTAttr , err := NewAttribute ( oid . AttributeSigningTime , time . Now ( ) )
if err != nil {
return err
}
si . SignedAttrs = append ( si . SignedAttrs , mdAttr , ctAttr , sTAttr )
2018-12-21 13:43:59 +00:00
si . SignedAttrs = append ( si . SignedAttrs , attrs ... )
2018-11-16 12:19:38 +00:00
sm , err := asn . MarshalWithParams ( si . SignedAttrs , ` set ` )
if err != nil {
return err
}
smd := hash . New ( )
if _ , errr := smd . Write ( sm ) ; errr != nil {
return errr
}
2018-12-21 13:43:59 +00:00
if si . Signature , err = signer . Sign ( rand . Reader , smd . Sum ( nil ) , signerOpts ) ; err != nil {
2018-11-16 12:19:38 +00:00
return err
}
sd . addDigestAlgorithm ( si . DigestAlgorithm )
sd . SignerInfos = append ( sd . SignerInfos , si )
return nil
}
// algorithmsForPublicKey takes an opinionated stance on what algorithms to use
// for the given public key.
func digestAlgorithmForPublicKey ( pub crypto . PublicKey ) pkix . AlgorithmIdentifier {
if ecPub , ok := pub . ( * ecdsa . PublicKey ) ; ok {
switch ecPub . Curve {
case elliptic . P384 ( ) :
return pkix . AlgorithmIdentifier { Algorithm : oid . DigestAlgorithmSHA384 }
case elliptic . P521 ( ) :
return pkix . AlgorithmIdentifier { Algorithm : oid . DigestAlgorithmSHA512 }
}
}
return pkix . AlgorithmIdentifier { Algorithm : oid . DigestAlgorithmSHA256 }
}
// ClearCertificates removes all certificates.
func ( sd * SignedData ) ClearCertificates ( ) {
sd . Certificates = [ ] asn1 . RawValue { }
}
// AddCertificate adds a *x509.Certificate.
func ( sd * SignedData ) AddCertificate ( cert [ ] byte ) error {
for _ , existing := range sd . Certificates {
if bytes . Equal ( existing . Bytes , cert ) {
return errors . New ( "certificate already added" )
}
}
var rv asn1 . RawValue
if _ , err := asn . Unmarshal ( cert , & rv ) ; err != nil {
return err
}
sd . Certificates = append ( sd . Certificates , rv )
return nil
}
// addDigestAlgorithm adds a new AlgorithmIdentifier if it doesn't exist yet.
func ( sd * SignedData ) addDigestAlgorithm ( algo pkix . AlgorithmIdentifier ) {
for _ , existing := range sd . DigestAlgorithms {
if existing . Algorithm . Equal ( algo . Algorithm ) {
return
}
}
sd . DigestAlgorithms = append ( sd . DigestAlgorithms , algo )
}
// X509Certificates gets the certificates, assuming that they're X.509 encoded.
func ( sd * SignedData ) X509Certificates ( ) ( map [ string ] * x509 . Certificate , error ) {
/ / Certificates field is optional . Handle missing value .
if sd . Certificates == nil {
return nil , nil
}
certs := map [ string ] * x509 . Certificate { }
// Empty set
if len ( sd . Certificates ) == 0 {
return certs , nil
}
for _ , raw := range sd . Certificates {
if raw . Class != asn1 . ClassUniversal || raw . Tag != asn1 . TagSequence {
return nil , ErrUnsupported
}
x509 , err := x509 . ParseCertificate ( raw . FullBytes )
if err != nil {
return nil , err
}
iasString , err := IASstring ( x509 )
certs [ iasString ] = x509
if err != nil {
return nil , err
}
}
return certs , nil
}
// ContentInfo returns the SignedData wrapped in a ContentInfo packet.
func ( sd * SignedData ) ContentInfo ( ) ( ContentInfo , error ) {
var nilCI ContentInfo
der , err := asn . Marshal ( * sd )
if err != nil {
return nilCI , err
}
return ContentInfo {
ContentType : oid . SignedData ,
Content : asn1 . RawValue {
Class : asn1 . ClassContextSpecific ,
Tag : 0 ,
Bytes : der ,
IsCompound : true ,
} ,
} , nil
}
// Verify checks the signature
func ( sd * SignedData ) Verify ( Opts x509 . VerifyOptions , detached [ ] byte ) ( chains [ ] [ ] [ ] * x509 . Certificate , err error ) {
certs , _ := sd . X509Certificates ( )
opts := Opts
for _ , c := range certs {
opts . Intermediates . AddCert ( c )
intermediates , fetchErr := fetchIntermediates ( c . IssuingCertificateURL )
for _ , e := range fetchErr {
fmt . Printf ( "Error while fetching intermediates: %s\n" , e )
}
for _ , i := range intermediates {
opts . Intermediates . AddCert ( i )
}
}
eContent := detached
if eContent == nil {
eContent = sd . EncapContentInfo . EContent
}
for _ , signer := range sd . SignerInfos {
//Find and check signer Certificate:
sidxxx , _ := signer . SID . IAS . RawValue ( )
sid := fmt . Sprintf ( "%x" , sidxxx . Bytes )
cert , exist := certs [ sid ]
if ! exist {
err = errors . New ( "Could not find a Certificate for signer with sid : " + sid )
return
}
2018-11-20 14:03:05 +00:00
var signingTime time . Time
signingTime , err = signer . GetSigningTimeAttribute ( )
if err != nil {
opts . CurrentTime = time . Now ( )
}
opts . CurrentTime = signingTime
2018-11-16 12:19:38 +00:00
var chain [ ] [ ] * x509 . Certificate
2018-11-20 14:03:05 +00:00
chain , err = cert . Verify ( opts )
2018-11-16 12:19:38 +00:00
if err != nil {
2018-11-20 14:03:05 +00:00
// return
2018-11-16 12:19:38 +00:00
}
signedMessage := eContent
if signer . SignedAttrs != nil {
//Hash message:
var hash crypto . Hash
hash , err = signer . Hash ( )
if err != nil {
return nil , err
}
md := hash . New ( )
_ , err = md . Write ( eContent )
if err != nil {
return nil , err
}
h := md . Sum ( nil )
var messageDigestAttr [ ] byte
messageDigestAttr , err = signer . GetMessageDigestAttribute ( )
if err != nil {
return
}
if ! bytes . Equal ( messageDigestAttr , h ) {
err = errors . New ( "Signed hash does not match the hash of the message" )
return
}
signedMessage , err = asn . MarshalWithParams ( signer . SignedAttrs , ` set ` )
if err != nil {
return
}
}
2018-12-10 16:18:29 +00:00
var sigAlg x509 . SignatureAlgorithm
sigAlg , err = signer . X509SignatureAlgorithm ( )
if err != nil {
return
}
2018-12-21 13:43:59 +00:00
switch signer . SignatureAlgorithm . Algorithm . String ( ) {
case oid . SignatureAlgorithmRSASSAPSS . String ( ) :
default :
err = cert . CheckSignature ( sigAlg , signedMessage , signer . Signature )
}
2018-11-16 12:19:38 +00:00
if err != nil {
return
}
chains = append ( chains , chain )
}
return
}
func fetchIntermediates ( urls [ ] string ) ( certificates [ ] * x509 . Certificate , errs [ ] error ) {
for _ , url := range urls {
var resp * http . Response
resp , err := http . Get ( url )
if err != nil {
errs = append ( errs , err )
continue
}
defer resp . Body . Close ( )
issuerBytes , err := ioutil . ReadAll ( resp . Body )
if err != nil {
errs = append ( errs , err )
continue
}
issuerCert , err := x509 . ParseCertificate ( issuerBytes )
if err != nil {
errs = append ( errs , err )
continue
}
//Prevent infinite loop
if len ( certificates ) > 50 {
err = errors . New ( "To many issuers" )
errs = append ( errs , err )
return
}
certificates = append ( certificates , issuerCert )
//Recusively fetch issuers
issuers , fetchErrs := fetchIntermediates ( issuerCert . IssuingCertificateURL )
certificates = append ( certificates , issuers ... )
errs = append ( errs , fetchErrs ... )
}
return
}