Files

488 lines
12 KiB
Go

package cms
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"math/big"
"sort"
"time"
"github.com/blacktop/go-plist"
"github.com/blacktop/ipsw/internal/codesign/cms/oid"
)
// CREDIT - https://github.com/github/smimesign/tree/main/ietf-cms
// AnySet is a helper for dealing with SET OF ANY types.
type AnySet struct {
Elements []asn1.RawValue `asn1:"set"`
}
// NewAnySet creates a new AnySet.
func NewAnySet(elts ...asn1.RawValue) AnySet {
return AnySet{elts}
}
// DecodeAnySet manually decodes a SET OF ANY type, since Go's parser can't
// handle them.
func DecodeAnySet(rv asn1.RawValue) (as AnySet, err error) {
if rv.Class != asn1.ClassUniversal || rv.Tag != asn1.TagSet {
err = fmt.Errorf("bad class: expecting %d, got %d", asn1.ClassUniversal, rv.Class)
return
}
der := rv.Bytes
for len(der) > 0 {
if der, err = asn1.Unmarshal(der, &rv); err != nil {
return
}
as.Elements = append(as.Elements, rv)
}
return
}
// Encode manually encodes a SET OF ANY type, since Go's parser can't handle
// them.
func (as AnySet) Encode(dst *asn1.RawValue) (err error) {
dst.Class = asn1.ClassUniversal
dst.Tag = asn1.TagSet
dst.IsCompound = true
var der []byte
for _, elt := range as.Elements {
if der, err = asn1.Marshal(elt); err != nil {
return
}
dst.Bytes = append(dst.Bytes, der...)
}
dst.FullBytes, err = asn1.Marshal(*dst)
return
}
type Attribute struct {
Type asn1.ObjectIdentifier
// This should be a SET OF ANY, but Go's asn1 parser can't handle slices of
// RawValues. Use value() to get an AnySet of the value.
RawValue asn1.RawValue
}
type Attributes []Attribute
func (attrs Attributes) MarshaledForSigning() ([]byte, error) {
seq, err := asn1.Marshal(struct {
Attributes `asn1:"set"`
}{attrs})
if err != nil {
return nil, err
}
// unwrap the outer SEQUENCE
var raw asn1.RawValue
if _, err = asn1.Unmarshal(seq, &raw); err != nil {
return nil, err
}
return raw.Bytes, nil
}
type SignerInfo struct {
Version int
SID asn1.RawValue
DigestAlgorithm pkix.AlgorithmIdentifier
SignedAttrs Attributes `asn1:"optional,tag:0"`
SignatureAlgorithm pkix.AlgorithmIdentifier
Signature []byte
UnsignedAttrs Attributes `asn1:"set,optional,tag:1"`
}
func (si SignerInfo) Hash() (crypto.Hash, error) {
algo := si.DigestAlgorithm.Algorithm.String()
hash := oid.DigestAlgorithmToCryptoHash[algo]
if hash == 0 || !hash.Available() {
return 0, fmt.Errorf("unsupported digest algorithm: %s", algo)
}
return hash, nil
}
type EncapsulatedContentInfo struct {
EContentType asn1.ObjectIdentifier
EContent asn1.RawValue `asn1:"optional,explicit,tag:0"`
}
func (eci EncapsulatedContentInfo) EContentValue() ([]byte, error) {
if eci.EContent.Bytes == nil {
return nil, nil
}
// The EContent is an `[0] EXPLICIT OCTET STRING`. EXPLICIT means that there
// is another whole tag wrapping the OCTET STRING. When we decoded the
// EContent into a asn1.RawValue we're just getting that outer tag, so the
// EContent.Bytes is the encoded OCTET STRING, which is what we really want
// the value of.
var octets asn1.RawValue
if rest, err := asn1.Unmarshal(eci.EContent.Bytes, &octets); err != nil {
return nil, err
} else if len(rest) > 0 {
return nil, fmt.Errorf("unexpected trailing data after EContent: %x", rest)
}
if octets.Class != asn1.ClassUniversal || octets.Tag != asn1.TagOctetString {
return nil, fmt.Errorf("unexpected EContent tag: %d; or class: %d", octets.Tag, octets.Class)
}
// While we already tried converting BER to DER, we didn't take constructed
// types into account. Constructed string types, as opposed to primitive
// types, can encode indefinite length strings by including a bunch of
// sub-strings that are joined together to get the actual value. Gpgsm uses
// a constructed OCTET STRING for the EContent, so we have to manually decode
// it here.
var value []byte
if octets.IsCompound {
rest := octets.Bytes
for len(rest) > 0 {
var err error
if rest, err = asn1.Unmarshal(rest, &octets); err != nil {
return nil, err
}
// Don't allow further constructed types.
if octets.Class != asn1.ClassUniversal || octets.Tag != asn1.TagOctetString || octets.IsCompound {
return nil, fmt.Errorf("unexpected EContent tag: %d; or class: %d", octets.Tag, octets.Class)
}
value = append(value, octets.Bytes...)
}
} else {
value = octets.Bytes
}
return value, nil
}
type SignedData struct {
Version int
DigestAlgorithms []pkix.AlgorithmIdentifier `asn1:"set"`
EncapContentInfo EncapsulatedContentInfo
Certificates []asn1.RawValue `asn1:"optional,set,tag:0"`
CRLs []asn1.RawValue `asn1:"optional,set,tag:1"`
SignerInfos []SignerInfo `asn1:"set"`
}
type IssuerAndSerialNumber struct {
Issuer asn1.RawValue
SerialNumber *big.Int
}
type ContentInfo struct {
ContentType asn1.ObjectIdentifier
Content asn1.RawValue `asn1:"explicit,tag:0"`
}
type AppleHashAgility struct {
Type asn1.ObjectIdentifier
Content asn1.RawValue `asn1:"explicit,tag:0"`
}
type CDHash struct {
CDHashes [][]byte `plist:"cdhashes,omitempty" xml:"cdhashes,omitempty"`
}
func NewSignedData(data []byte) (*SignedData, error) {
octets, err := asn1.Marshal(asn1.RawValue{
Class: asn1.ClassUniversal,
Tag: asn1.TagOctetString,
Bytes: data,
IsCompound: false,
})
if err != nil {
return nil, err
}
return &SignedData{
Version: 1,
DigestAlgorithms: []pkix.AlgorithmIdentifier{},
EncapContentInfo: EncapsulatedContentInfo{
EContentType: oid.ContentTypeData,
EContent: asn1.RawValue{
Class: asn1.ClassContextSpecific,
Tag: 0,
Bytes: octets,
IsCompound: true,
},
},
SignerInfos: []SignerInfo{},
}, nil
}
func (sd *SignedData) Sign(chain []*x509.Certificate, privateKey any) error {
var certPub []byte
var cert *x509.Certificate
signer, ok := privateKey.(crypto.Signer)
if !ok {
return errors.New("private key does not implement crypto.Signer")
}
pub, err := x509.MarshalPKIXPublicKey(signer.Public())
if err != nil {
return err
}
for _, c := range chain {
if err = sd.AddCertificate(c); err != nil {
return err
}
if certPub, err = x509.MarshalPKIXPublicKey(c.PublicKey); err != nil {
return err
}
if bytes.Equal(pub, certPub) {
cert = c
}
}
if cert == nil {
return errors.New("no certificate found for public key")
}
sid, err := newIssuerAndSerialNumber(cert)
if err != nil {
return err
}
digestAlgorithmID := digestAlgorithmForPublicKey(pub)
signatureAlgorithmOID, ok := oid.X509PublicKeyAndDigestAlgorithmToSignatureAlgorithm[cert.PublicKeyAlgorithm][digestAlgorithmID.Algorithm.String()]
if !ok {
return errors.New("unsupported certificate public key algorithm")
}
signatureAlgorithmID := pkix.AlgorithmIdentifier{Algorithm: signatureAlgorithmOID}
si := SignerInfo{
Version: 1,
SID: sid,
DigestAlgorithm: digestAlgorithmID,
SignedAttrs: nil,
SignatureAlgorithm: signatureAlgorithmID,
Signature: nil,
UnsignedAttrs: nil,
}
// Get the message
content, err := sd.EncapContentInfo.EContentValue()
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
}
md := hash.New()
if _, err = md.Write(content); err != nil {
return err
}
pldata, err := plist.MarshalIndent(CDHash{
CDHashes: [][]byte{md.Sum(nil)[:20]},
}, plist.XMLFormat, "\t")
if err != nil {
return err
}
// Build our SignedAttributes
stAttr, err := NewAttribute(oid.AttributeSigningTime, time.Now().UTC())
if err != nil {
return err
}
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
}
hvAttr, err := NewAttribute(oid.AttributeAppleHashAgilityV1, pldata)
if err != nil {
return err
}
hv2Attr, err := NewAttribute(oid.AttributeAppleHashAgilityV2, AppleHashAgility{
Type: oid.DigestAlgorithmSHA256,
Content: asn1.RawValue{
Class: asn1.ClassUniversal,
Tag: asn1.TagOctetString,
Bytes: md.Sum(nil),
IsCompound: false,
},
})
if err != nil {
return err
}
// sort attributes to match required order in marshaled form
si.SignedAttrs, err = sortAttributes(stAttr, mdAttr, ctAttr, hvAttr, hv2Attr)
if err != nil {
return err
}
// Signature is over the marshaled signed attributes
sm, err := si.SignedAttrs.MarshaledForSigning()
if err != nil {
return err
}
smd := hash.New()
if _, err := smd.Write(sm); err != nil {
return err
}
if si.Signature, err = signer.Sign(rand.Reader, smd.Sum(nil), hash); err != nil {
return err
}
sd.addDigestAlgorithm(si.DigestAlgorithm)
sd.SignerInfos = append(sd.SignerInfos, si)
return nil
}
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)
}
func (sd *SignedData) AddCertificate(cert *x509.Certificate) error {
for _, c := range sd.Certificates {
if bytes.Equal(c.Bytes, cert.Raw) {
return nil
}
}
var rv asn1.RawValue
if _, err := asn1.Unmarshal(cert.Raw, &rv); err != nil {
return err
}
sd.Certificates = append(sd.Certificates, rv)
return nil
}
func (sd *SignedData) AddTimestamps(url, proxy string, insecure bool) error {
var err error
var attrs = make([]Attribute, len(sd.SignerInfos))
for i := range attrs {
if attrs[i], err = GetTimestamp(url, proxy, insecure, sd.SignerInfos[i]); err != nil {
return err
}
}
for i := range attrs {
sd.SignerInfos[i].UnsignedAttrs = append(sd.SignerInfos[i].UnsignedAttrs, attrs[i])
}
return nil
}
func (sd *SignedData) Detached() {
sd.EncapContentInfo.EContent = asn1.RawValue{}
}
func (sd *SignedData) ToDER() ([]byte, error) {
der, err := asn1.Marshal(*sd)
if err != nil {
return nil, err
}
ci := ContentInfo{
ContentType: oid.ContentTypeSignedData,
Content: asn1.RawValue{
Class: asn1.ClassContextSpecific,
Tag: 0,
Bytes: der,
IsCompound: true,
},
}
return asn1.Marshal(ci)
}
func NewAttribute(typ asn1.ObjectIdentifier, val any) (attr Attribute, err error) {
var der []byte
if der, err = asn1.Marshal(val); err != nil {
return
}
var rv asn1.RawValue
if _, err = asn1.Unmarshal(der, &rv); err != nil {
return
}
if err = NewAnySet(rv).Encode(&attr.RawValue); err != nil {
return
}
attr.Type = typ
return
}
func newIssuerAndSerialNumber(cert *x509.Certificate) (rv asn1.RawValue, err error) {
sid := IssuerAndSerialNumber{
SerialNumber: new(big.Int).Set(cert.SerialNumber),
}
if _, err = asn1.Unmarshal(cert.RawIssuer, &sid.Issuer); err != nil {
return
}
var der []byte
if der, err = asn1.Marshal(sid); err != nil {
return
}
if _, err = asn1.Unmarshal(der, &rv); err != nil {
return
}
return
}
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}
}
func sortAttributes(attrs ...Attribute) ([]Attribute, error) {
// Sort attrs by their encoded values (including tag and
// lengths) as specified in X690 Section 11.6 and implemented
// in go >= 1.15's asn1.Marshal().
sort.Slice(attrs, func(i, j int) bool {
return bytes.Compare(
attrs[i].RawValue.FullBytes,
attrs[j].RawValue.FullBytes) < 0
})
return attrs, nil
}