diff --git a/datastructures/signerrequest.go b/datastructures/signerrequest.go index d1035bc..e477c7e 100644 --- a/datastructures/signerrequest.go +++ b/datastructures/signerrequest.go @@ -18,9 +18,9 @@ type SignerRequest struct { MdAlgorithm shared.MessageDigestAlgorithmId Days uint16 Spkac uint8 - Content1 string - Content2 string - Content3 string + Content1 []byte + Content2 []byte + Content3 []byte } func SignerRequestFromData(blockData []byte) (*SignerRequest, error) { @@ -29,15 +29,15 @@ func SignerRequestFromData(blockData []byte) (*SignerRequest, error) { contentBytes := blockData[3+headerLength:] content1Length := Decode24BitLength(contentBytes[0:3]) - content1 := string(contentBytes[3 : 3+content1Length]) + content1 := contentBytes[3 : 3+content1Length] content2Offset := 3 + content1Length content2Length := Decode24BitLength(contentBytes[content2Offset : content2Offset+3]) - content2 := string(contentBytes[3+content2Offset : 3+content2Offset+content2Length]) + content2 := contentBytes[3+content2Offset : 3+content2Offset+content2Length] content3Offset := 3 + content2Offset + content2Length content3Length := Decode24BitLength(contentBytes[content3Offset : content3Offset+3]) - content3 := string(contentBytes[3+content3Offset : 3+content3Offset+content3Length]) + content3 := contentBytes[3+content3Offset : 3+content3Offset+content3Length] return &SignerRequest{ Version: headerBytes[0], @@ -67,9 +67,9 @@ func (r *SignerRequest) Serialize() []byte { byte(r.MdAlgorithm), }, parameter2Bytes, {r.Spkac}}, []byte{}) - content1Bytes := []byte(r.Content1) - content2Bytes := []byte(r.Content2) - content3Bytes := []byte(r.Content3) + content1Bytes := r.Content1 + content2Bytes := r.Content2 + content3Bytes := r.Content3 blockBytes := bytes.Join([][]byte{ Encode24BitLength(headerBytes), headerBytes, Encode24BitLength(content1Bytes), content1Bytes, @@ -100,6 +100,6 @@ func NewNulRequest() *SignerRequest { return &SignerRequest{ Version: shared.ProtocolVersion, Action: shared.ActionNul, - Content1: time.Now().UTC().Format(signerTimeFormat), + Content1: []byte(time.Now().UTC().Format(signerTimeFormat)), } } diff --git a/datastructures/signerresponse.go b/datastructures/signerresponse.go index 9bfea36..d9f1ec2 100644 --- a/datastructures/signerresponse.go +++ b/datastructures/signerresponse.go @@ -13,9 +13,9 @@ type SignerResponse struct { Action shared.Action Reserved1 uint8 Reserved2 uint8 - Content1 string - Content2 string - Content3 string + Content []byte + Argument1 []byte + Argument2 []byte } func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) (*SignerResponse, error) { @@ -29,14 +29,14 @@ func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) headerBytes := blockData[offset : offset+headerLength] offset += headerLength - content := make([]string, 3) + content := make([][]byte, 3) for offset < len(blockData) { dataLength := Decode24BitLength(blockData[offset : offset+3]) if len(blockData)-3 < dataLength { return nil, errors.New("structure cut off") } offset += 3 - content = append(content, string(blockData[offset:offset+dataLength])) + content = append(content, blockData[offset:offset+dataLength]) offset += dataLength } @@ -50,32 +50,49 @@ func SignerResponseFromData(lengthBytes []byte, blockData []byte, checkSum byte) Action: shared.Action(headerBytes[1]), Reserved1: headerBytes[2], Reserved2: headerBytes[3], - Content1: content[0], - Content2: content[1], - Content3: content[2], + Content: content[0], + Argument1: content[1], + Argument2: content[2], }, nil } func (r SignerResponse) Serialize() []byte { headerBytes := []byte{r.Version, byte(r.Action), r.Reserved1, r.Reserved2} - content1Bytes := []byte(r.Content1) - content2Bytes := []byte(r.Content2) - content3Bytes := []byte(r.Content3) blockBytes := bytes.Join([][]byte{ Encode24BitLength(headerBytes), headerBytes, - Encode24BitLength(content1Bytes), content1Bytes, - Encode24BitLength(content2Bytes), content2Bytes, - Encode24BitLength(content3Bytes), content3Bytes, + Encode24BitLength(r.Content), r.Content, + Encode24BitLength(r.Argument1), r.Argument1, + Encode24BitLength(r.Argument2), r.Argument2, }, []byte{}) return bytes.Join([][]byte{Encode24BitLength(blockBytes), blockBytes}, []byte{}) } func NewNulResponse(version byte) *SignerResponse { return &SignerResponse{ - Version: version, - Action: shared.ActionNul, - Content1: "", - Content2: "", - Content3: "", + Version: version, + Action: shared.ActionNul, + Content: []byte{}, + Argument1: []byte{}, + Argument2: []byte{}, + } +} + +func NewRevokeResponse(version byte, content []byte) *SignerResponse { + return &SignerResponse{ + Version: version, + Action: shared.ActionRevoke, + Content: content, + Argument1: []byte{}, + Argument2: []byte{}, + } +} + +func NewSignResponse(version byte, content []byte) *SignerResponse { + return &SignerResponse{ + Version: version, + Action: shared.ActionSign, + Content: content, + Argument1: []byte{}, + Argument2: []byte{}, } } diff --git a/shared/io.go b/shared/io.go index 5cb06dc..8ae89d7 100644 --- a/shared/io.go +++ b/shared/io.go @@ -1,6 +1,7 @@ package shared import ( + "bytes" "fmt" "io" "time" @@ -13,31 +14,36 @@ func ReceiveBytes(port io.Reader, count int, timeout time.Duration) ([]byte, err readCh := make(chan []byte, 1) errCh := make(chan error, 1) go func() { - sumRead := 0 - for { - data := make([]byte, count) + buffer := bytes.NewBuffer([]byte{}) + for remainder := count; remainder > 0; { + data := make([]byte, remainder) if readBytes, err := port.Read(data); err != nil { errCh <- err + return } else if readBytes > 0 { - log.Tracef("%d bytes read", readBytes) - sumRead += readBytes - readCh <- data[0:readBytes] - } else { - readCh <- make([]byte, 0) - } - if sumRead >= count { - break + buffer.Write(data[0:readBytes]) + remainder -= readBytes + log.Tracef("%d bytes read, remaining %d", readBytes, remainder) } } + readCh <- buffer.Bytes() + close(readCh) }() + + buffer := bytes.NewBuffer([]byte{}) select { case <-time.After(timeout): return nil, fmt.Errorf("timeout passed %v", timeout) case err := <-errCh: return nil, err case data := <-readCh: - return data, nil + log.Tracef("received %d bytes from channel", len(data)) + if data == nil { + break + } + buffer.Write(data) } + return buffer.Bytes(), nil } func SendBytes(port io.Writer, data []byte) error { diff --git a/signer/command_processor.go b/signer/command_processor.go index 97f2d47..0d6aae5 100644 --- a/signer/command_processor.go +++ b/signer/command_processor.go @@ -2,18 +2,15 @@ package signer import ( "bytes" - "crypto/rand" - "crypto/sha1" "crypto/x509" - "crypto/x509/pkix" "encoding/hex" "encoding/pem" "errors" "fmt" "io/ioutil" "os" - "path" - "time" + "os/exec" + "strings" "unsafe" log "github.com/sirupsen/logrus" @@ -21,20 +18,14 @@ import ( "git.cacert.org/cacert-gosigner/datastructures" "git.cacert.org/cacert-gosigner/shared" + "git.cacert.org/cacert-gosigner/signer/x509_ops" ) -const crlLifetime = time.Hour * 24 * 7 - -var emptyDataSha1 [20]byte - -func init() { - emptyDataSha1 = sha1.Sum([]byte{}) -} - type CommandProcessorSettings struct { - CABaseDir string - XDeltaPath string - OpenPGPUidEmail string + CABaseDir string + XDeltaPath string + OpenPGPKeyRingDir string + OpenPGPUidEmail string } // The CommandProcessor takes parsed protocol data and executes the actual @@ -102,7 +93,36 @@ func (p *CommandProcessor) handleSignAction( error, ) { log.Debugf("handle sign call: %v", command) - return nil, errors.New("not implemented yet") + + idSystem, err := p.checkIdentitySystem( + command.System, command.Root, command.Profile, command.MdAlgorithm) + if err != nil { + return nil, err + } + log.Debugf("identified id system: %+v", idSystem) + + switch command.System { + case CsX509: + request := command.Content1 + san := command.Content2 + subject := command.Content3 + + if content, err := p.signX509Certificate(idSystem, command.Days, command.Spkac, request, san, subject); err != nil { + return nil, err + } else { + return datastructures.NewSignResponse(command.Version, content), nil + } + case CsOpenPGP: + pubKey := command.Content1 + + if content, err := p.signOpenpgpKey(idSystem, command.Days, pubKey); err != nil { + return nil, err + } else { + return datastructures.NewSignResponse(command.Version, content), nil + } + default: + return nil, fmt.Errorf("sign not implemented for crypto system %s", idSystem.System) + } } func (p *CommandProcessor) handleRevokeAction( @@ -128,7 +148,11 @@ func (p *CommandProcessor) handleRevokeAction( case CsX509: request := command.Content1 clientHash := command.Content3 - return p.revokeX509(idSystem, request, clientHash) + if content, err := p.revokeX509(idSystem, request, clientHash); err != nil { + return nil, err + } else { + return datastructures.NewRevokeResponse(command.Version, content), nil + } default: return nil, fmt.Errorf("revoke not implemented for crypto system %s", idSystem.System) } @@ -136,10 +160,9 @@ func (p *CommandProcessor) handleRevokeAction( type IdSystemParameters struct { System *CryptoSystem - Root *RootCredentials - RootId shared.CryptoSystemRootId - Profile string - MessageDigestAlgorithm x509.SignatureAlgorithm + Root interface{} + Profile interface{} + MessageDigestAlgorithm interface{} } func (p *CommandProcessor) checkIdentitySystem( @@ -158,7 +181,7 @@ func (p *CommandProcessor) checkIdentitySystem( root, ok := p.CryptoSystems[systemId].Roots[rootId] if !ok { return nil, fmt.Errorf( - "unsupported root certificate %d for crypto system %s", + "unsupported root %d for crypto system %s", rootId, cryptoSystem, ) @@ -166,7 +189,7 @@ func (p *CommandProcessor) checkIdentitySystem( profile, ok := p.CryptoSystems[systemId].Profiles[profileId] if !ok { return nil, fmt.Errorf( - "invalid CA profile %d for crypto system %s", + "invalid profile %d for crypto system %s", profileId, cryptoSystem, ) @@ -182,153 +205,111 @@ func (p *CommandProcessor) checkIdentitySystem( return &IdSystemParameters{ System: cryptoSystem, Root: root, - RootId: rootId, Profile: profile, MessageDigestAlgorithm: mdAlgorithm, }, nil } -func (p *CommandProcessor) revokeX509( - system *IdSystemParameters, - request string, - clientHash string, -) (*datastructures.SignerResponse, error) { - clientHashBytes, err := hex.DecodeString(clientHash) - if err != nil { - return nil, fmt.Errorf("could not parse '%s' as hex bytes", clientHash) - } +func (p *CommandProcessor) revokeX509(system *IdSystemParameters, request []byte, clientHash []byte) ([]byte, error) { + x509Root := system.Root.(*x509_ops.Root) + signatureAlgorithm := system.MessageDigestAlgorithm.(x509.SignatureAlgorithm) - log.Debugf("revoke X.509 for root %s (%d)", system.Root, system.RootId) - log.Debugf("hash bytes from client %x", clientHashBytes) + log.Debugf("revoke X.509 for root %s", x509Root) + log.Debugf("hash bytes from client %x", clientHash) - crlFileName := fmt.Sprintf("revoke-root%d.crl", system.RootId) - stat, err := os.Stat(crlFileName) - if err != nil { - log.Debugf("CRL file %s does not exist", crlFileName) - } else if stat.IsDir() { - return nil, fmt.Errorf("CRL filename %s points to a directory", crlFileName) - } - - currentHash := emptyDataSha1 - if stat != nil { - crlData, err := ioutil.ReadFile(crlFileName) - if err != nil { - log.Warnf("could not read current CRL, assuming empty CRL") - } - currentHash = sha1.Sum(crlData) - } - log.Debugf("hash bytes on signer %x", currentHash) - - crlIsCurrent := false - if bytes.Equal(clientHashBytes, currentHash[:]) { - log.Debug("client CRL hash and current CRL hash on signer match") - err := p.deleteOldCrl(system.RootId, clientHash) - if err != nil { - log.Warnf("could not delete old CRLs: %v", err) - } - if !bytes.Equal(clientHashBytes, emptyDataSha1[:]) { - crlIsCurrent = true - } else { - log.Debugf("client has an empty CRL, the signer too") - } - } + var ( + err error + crlBytes []byte + newHash *[20]byte + ) if len(request) > 0 { - _, err = revokeCertificate(request, system.System, system.RootId) + _, err = x509Root.RevokeCertificate(request) if err != nil { return nil, fmt.Errorf("could not revoke certificate / create CRL: %v", err) } } - var crlBytes []byte - crlBytes, err = generateCrl(system.MessageDigestAlgorithm, system.System, system.RootId) + crlBytes, newHash, err = x509Root.GenerateCrl(signatureAlgorithm) if err != nil { - return nil, fmt.Errorf("could not generate a new CRL for root %d: %v", system.RootId, err) + return nil, fmt.Errorf("could not generate a new CRL for root %s: %v", x509Root, err) } - log.Debugf("crlIsCurrent: %v, crlBytes: %d", crlIsCurrent, len(crlBytes)) + log.Debugf("crlBytes: %d", len(crlBytes)) - log.Tracef("New CRL\n%s", pem.EncodeToMemory(&pem.Block{ - Type: "X509 CRL", - Bytes: crlBytes, - })) + var content []byte + oldCrlFile := x509Root.GetCrlFileName(string(clientHash)) + newCrlFile := x509Root.GetCrlFileName(hex.EncodeToString(newHash[:])) - err = ioutil.WriteFile(crlFileName, crlBytes, 0644) - if err != nil { - return nil, fmt.Errorf("could not write new CRL to %s: %v", crlFileName, err) + statOld, oldErr := os.Stat(oldCrlFile) + statNew, newErr := os.Stat(newCrlFile) + + if oldErr == nil && newErr == nil && !statOld.IsDir() && !statNew.IsDir() { + content, err = p.buildXDelta(oldCrlFile, newCrlFile) + if err != nil { + log.Warnf("could not generate xdelta: %v", err) + } + log.Tracef("xdelte produced %d bytes", len(content)) + } + if content == nil { + content = pem.EncodeToMemory(&pem.Block{ + Type: "X509 CRL", + Bytes: crlBytes, + }) + log.Tracef("sending %d PEM CRL bytes", len(content)) } - return nil, errors.New("not implemented yet") + return content, nil } -func (p *CommandProcessor) deleteOldCrl(id shared.CryptoSystemRootId, hash string) error { - path.Join(p.Settings.CABaseDir, "currentcrls", fmt.Sprintf("%d", id)) - // TODO: implement - return errors.New("not implemented yet") -} - -func generateCrl( - algorithm x509.SignatureAlgorithm, - system *CryptoSystem, - rootId shared.CryptoSystemRootId, -) ([]byte, error) { - caCertificate, err := system.loadCertificate(rootId) +func (p *CommandProcessor) buildXDelta(oldFile string, newFile string) ([]byte, error) { + patchFile, err := ioutil.TempFile(os.TempDir(), "*.patch") if err != nil { - return nil, err + return nil, fmt.Errorf("could not create temporary file for patch: %v", err) } - caPrivateKey, err := system.getPrivateKey(rootId) - if err != nil { - return nil, err - } - certificatesToRevoke, err := system.loadRevokedCertificatesFromDatabase(rootId) - if err != nil { - return nil, err - } - nextCrlNumber, err := system.getNextCRLNumber(rootId) - if err != nil { - return nil, err - } - crlTemplate := &x509.RevocationList{ - SignatureAlgorithm: algorithm, - RevokedCertificates: certificatesToRevoke, - Number: nextCrlNumber, - ThisUpdate: time.Now(), - NextUpdate: time.Now().Add(crlLifetime), - ExtraExtensions: nil, - } - defer func() { - if err = system.bumpCRLNumber(rootId, nextCrlNumber); err != nil { - log.Errorf("could not bump CRL number: %v", err) + if err := os.Remove(patchFile.Name()); err != nil { + log.Warnf("could not remove temporary file %s: %v", patchFile.Name(), err) } }() - - return x509.CreateRevocationList( - rand.Reader, - crlTemplate, - caCertificate, - caPrivateKey, - ) -} - -func revokeCertificate(request string, system *CryptoSystem, rootId shared.CryptoSystemRootId) (*pkix.RevokedCertificate, error) { - pemBlock, _ := pem.Decode([]byte(request)) - if pemBlock.Type != "CERTIFICATE" { - if pemBlock.Type != "CERTIFICATE" { - log.Warnf( - "PEM structure is probably not a certificate. PEM block has type %s", - pemBlock.Type, + if err = patchFile.Close(); err != nil { + return nil, fmt.Errorf("could not close temporary file: %v", err) + } + buf := bytes.NewBuffer([]byte{}) + cmd := exec.Command(p.Settings.XDeltaPath, "delta", oldFile, newFile, patchFile.Name()) + cmd.Stdout = buf + cmd.Stderr = buf + err = cmd.Run() + if err != nil { + switch err.(type) { + case *exec.ExitError: + if err.(*exec.ExitError).ExitCode() == 1 { + // xdelta delta exits with status code 1 if a delta has been found + break + } + return nil, fmt.Errorf( + "xdelta command '%s' did not work correctly: %v\noutput was:\n%s", + strings.Join(cmd.Args, " "), + err, + buf.String(), + ) + default: + return nil, fmt.Errorf( + "xdelta command '%s' did not work correctly: %v\noutput was:\n%s", + strings.Join(cmd.Args, " "), + err, + buf.String(), ) - log.Trace(request) } } - certificate, err := x509.ParseCertificate(pemBlock.Bytes) - if err != nil { - return nil, fmt.Errorf( - "could no parse certificate: %v", - err, - ) - } - return system.recordRevocation(rootId, certificate) + return ioutil.ReadFile(patchFile.Name()) +} + +func (p *CommandProcessor) signX509Certificate(system *IdSystemParameters, days uint16, spkac uint8, request []byte, san []byte, subject []byte) ([]byte, error) { + return nil, errors.New("signX509Certificate is not implemented yet") +} + +func (p *CommandProcessor) signOpenpgpKey(system *IdSystemParameters, days uint16, pubKey []byte) ([]byte, error) { + return nil, errors.New("signOpenpgpKey is not implemented yet") } func NewCommandProcessorSettings() *CommandProcessorSettings { @@ -336,9 +317,18 @@ func NewCommandProcessorSettings() *CommandProcessorSettings { if !ok { caBasedir = "." } - gpgUidEmail, ok := os.LookupEnv("SIGNER_GPG_KEYRING_DIR") + gpgKeyringDir, ok := os.LookupEnv("SIGNER_GPG_KEYRING_DIR") + if !ok { + gpgKeyringDir = "." + } + gpgUidEmail, ok := os.LookupEnv("SIGNER_GPG_ID") if !ok { gpgUidEmail = "gpg@cacert.org" } - return &CommandProcessorSettings{CABaseDir: caBasedir, OpenPGPUidEmail: gpgUidEmail, XDeltaPath: "/usr/bin/xdelta"} + return &CommandProcessorSettings{ + CABaseDir: caBasedir, + OpenPGPKeyRingDir: gpgKeyringDir, + OpenPGPUidEmail: gpgUidEmail, + XDeltaPath: "/usr/bin/xdelta", + } } diff --git a/signer/common/helpers.go b/signer/common/helpers.go new file mode 100644 index 0000000..78c520f --- /dev/null +++ b/signer/common/helpers.go @@ -0,0 +1,17 @@ +package common + +import ( + "fmt" + "math/big" + "strconv" + "strings" +) + +func StringAsBigInt(data []byte) (*big.Int, error) { + dataString := strings.TrimSpace(string(data)) + parseInt, err := strconv.ParseInt(dataString, 16, 64) + if err != nil { + return nil, fmt.Errorf("could not parse %s as big int: %v", dataString, err) + } + return big.NewInt(parseInt), nil +} diff --git a/signer/crypto_system.go b/signer/crypto_system.go index c33f779..23d1220 100644 --- a/signer/crypto_system.go +++ b/signer/crypto_system.go @@ -1,251 +1,16 @@ package signer import ( - "bufio" - "crypto" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "io/ioutil" - "math/big" - "os" - "path" - "strconv" - "strings" - "time" - - log "github.com/sirupsen/logrus" - "git.cacert.org/cacert-gosigner/shared" ) -type RootCredentials struct { - Name string - PrivateKeyFile string - PublicKeyFile string - CertificateFile string - DatabaseFile string - CRLNumber string -} - type CryptoSystem struct { Name string - Roots map[shared.CryptoSystemRootId]*RootCredentials - Profiles map[shared.CertificateProfileId]string - DigestAlgorithms map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm + Roots map[shared.CryptoSystemRootId]interface{} + Profiles map[shared.CertificateProfileId]interface{} + DigestAlgorithms map[shared.MessageDigestAlgorithmId]interface{} } func (system CryptoSystem) String() string { return system.Name } - -func (system CryptoSystem) loadCertificate(rootId shared.CryptoSystemRootId) (*x509.Certificate, error) { - certificateFile := system.Roots[rootId].CertificateFile - pemBytes, err := ioutil.ReadFile(certificateFile) - if err != nil { - return nil, fmt.Errorf( - "could not load certificate %s: %v", - certificateFile, - err, - ) - } - pemBlock, _ := pem.Decode(pemBytes) - if pemBlock.Type != "CERTIFICATE" { - log.Warnf( - "PEM in %s is probably not a certificate. PEM block has type %s", - certificateFile, - pemBlock.Type, - ) - } - certificate, err := x509.ParseCertificate(pemBlock.Bytes) - if err != nil { - return nil, fmt.Errorf( - "could no parse certificate from %s: %v", - certificateFile, - err, - ) - } - return certificate, nil -} - -func (system CryptoSystem) getPrivateKey(rootId shared.CryptoSystemRootId) (crypto.Signer, error) { - privateKeyFile := system.Roots[rootId].PrivateKeyFile - pemBytes, err := ioutil.ReadFile(privateKeyFile) - if err != nil { - return nil, fmt.Errorf( - "could not load private key %s: %v", - privateKeyFile, - err, - ) - } - pemBlock, _ := pem.Decode(pemBytes) - if pemBlock.Type != "PRIVATE KEY" { - log.Warnf( - "PEM in %s is probably not a private key. PEM block has type %s", - privateKeyFile, - pemBlock.Type, - ) - } - privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) - if err != nil { - return nil, fmt.Errorf( - "could no parse private key from %s: %v", - privateKeyFile, - err, - ) - } - return privateKey.(*rsa.PrivateKey), nil -} - -func (system *CryptoSystem) getNextCRLNumber(rootId shared.CryptoSystemRootId) (*big.Int, error) { - crlNumberFile := system.Roots[rootId].CRLNumber - _, err := os.Stat(crlNumberFile) - if err != nil { - log.Warnf("CRL number file %s does not exist: %v", crlNumberFile, err) - return big.NewInt(1), nil - } - data, err := ioutil.ReadFile(crlNumberFile) - if err != nil { - return nil, fmt.Errorf("could not read CRL number file %s", crlNumberFile) - } - result, err := stringAsBigInt(data) - if err != nil { - return nil, fmt.Errorf("could not parse content of %s as CRL number: %v", crlNumberFile, err) - } - return result, nil -} - -func (system *CryptoSystem) bumpCRLNumber(rootId shared.CryptoSystemRootId, current *big.Int) error { - serial := current.Int64() + 1 - crlNumberFile := system.Roots[rootId].CRLNumber - outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt") - defer func() { _ = outFile.Close() }() - - _, err = outFile.WriteString(fmt.Sprintf( - "%s\n", - strings.ToUpper(strconv.FormatInt(serial, 16)), - )) - if err != nil { - return fmt.Errorf("could not write new CRL number %d to %s: %v", serial, outFile.Name(), err) - } - - if err = outFile.Close(); err != nil { - return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) - } - if err = os.Rename(crlNumberFile, fmt.Sprintf("%s.old", crlNumberFile)); err != nil { - return fmt.Errorf("could not rename %s to %s.old: %v", crlNumberFile, crlNumberFile, err) - } - if err = os.Rename(outFile.Name(), crlNumberFile); err != nil { - return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), crlNumberFile, err) - } - return nil -} - -func stringAsBigInt(data []byte) (*big.Int, error) { - dataString := strings.TrimSpace(string(data)) - parseInt, err := strconv.ParseInt(dataString, 16, 64) - if err != nil { - return nil, fmt.Errorf("could not parse %s as big int: %v", dataString, err) - } - return big.NewInt(parseInt), nil -} - -func (system *CryptoSystem) loadRevokedCertificatesFromDatabase(rootId shared.CryptoSystemRootId) ([]pkix.RevokedCertificate, error) { - databaseFile := system.Roots[rootId].DatabaseFile - _, err := os.Stat(databaseFile) - if err != nil { - log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err) - return []pkix.RevokedCertificate{}, nil - } - file, err := os.Open(databaseFile) - if err != nil { - return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err) - } - defer func() { _ = file.Close() }() - - result := make([]pkix.RevokedCertificate, 0) - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := strings.Split(scanner.Text(), "\t") - if line[0] == "R" { - serialNumber, err := stringAsBigInt([]byte(line[3])) - if err != nil { - return nil, fmt.Errorf("could not parse serial number %s as big int: %v", line[3], err) - } - revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64) - result = append(result, pkix.RevokedCertificate{ - SerialNumber: serialNumber, - RevocationTime: time.Unix(revokeTs, 0), - Extensions: nil, - }) - } - } - return result, nil -} - -func (system *CryptoSystem) recordRevocation(rootId shared.CryptoSystemRootId, certificate *x509.Certificate) (*pkix.RevokedCertificate, error) { - databaseFile := system.Roots[rootId].DatabaseFile - _, err := os.Stat(databaseFile) - if err != nil { - return nil, fmt.Errorf("openssl certificate database file %s does not exist: %v", databaseFile, err) - } - inFile, err := os.Open(databaseFile) - if err != nil { - return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err) - } - defer func() { _ = inFile.Close() }() - - outFile, err := ioutil.TempFile(path.Dir(databaseFile), "*.txt") - defer func() { _ = outFile.Close() }() - - scanner := bufio.NewScanner(inFile) - writer := bufio.NewWriter(outFile) - - found := false - revocationTime := time.Now() - - for scanner.Scan() { - line := scanner.Text() - parts := strings.Split(line, "\t") - serialNumber, err := stringAsBigInt([]byte(parts[3])) - if err != nil { - return nil, fmt.Errorf("could not parse serial number %s as big int: %v", parts[3], err) - } - if serialNumber == certificate.SerialNumber { - line = strings.Join( - []string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]}, - "\t", - ) - found = true - } - if _, err = writer.WriteString(fmt.Sprintf("%s\n", line)); err != nil { - return nil, fmt.Errorf("could not write '%s' to %s: %v", line, outFile.Name(), err) - } - } - - if err = outFile.Close(); err != nil { - return nil, fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) - } - if err = inFile.Close(); err != nil { - return nil, fmt.Errorf("could not close %s: %v", databaseFile, err) - } - - if err = os.Rename(databaseFile, fmt.Sprintf("%s.old", databaseFile)); err != nil { - return nil, fmt.Errorf("could not rename %s to %s.old: %v", databaseFile, databaseFile, err) - } - - if err = os.Rename(outFile.Name(), databaseFile); err != nil { - return nil, fmt.Errorf("could not rename temporary file %s to %s: %v", outFile.Name(), databaseFile, err) - } - - if !found { - log.Warnf("entry not found in database") - } - - return &pkix.RevokedCertificate{ - SerialNumber: certificate.SerialNumber, - RevocationTime: revocationTime, - }, nil -} diff --git a/signer/openpgp_ops/operations.go b/signer/openpgp_ops/operations.go new file mode 100644 index 0000000..2a51ee3 --- /dev/null +++ b/signer/openpgp_ops/operations.go @@ -0,0 +1,11 @@ +package openpgp_ops + +type OpenPGPRoot struct { + Name string + SecretKeyRing string + Identifier string +} + +type OpenPGPProfile struct { + Name string +} diff --git a/signer/protocol_elements.go b/signer/protocol_elements.go index 71491b8..c39710e 100644 --- a/signer/protocol_elements.go +++ b/signer/protocol_elements.go @@ -1,9 +1,14 @@ package signer import ( + "crypto" "crypto/x509" + "fmt" + "path" "git.cacert.org/cacert-gosigner/shared" + "git.cacert.org/cacert-gosigner/signer/openpgp_ops" + "git.cacert.org/cacert-gosigner/signer/x509_ops" ) const ( @@ -36,13 +41,13 @@ const ( ) const ( - X509MDDefault shared.MessageDigestAlgorithmId = 0 - X509MDMd5 shared.MessageDigestAlgorithmId = 1 - X509MDSha1 shared.MessageDigestAlgorithmId = 2 - X509MDRipeMD160 shared.MessageDigestAlgorithmId = 3 - X509MDSha256 shared.MessageDigestAlgorithmId = 8 - X509MDSha384 shared.MessageDigestAlgorithmId = 9 - X509MDSha512 shared.MessageDigestAlgorithmId = 10 + X509MDDefault shared.MessageDigestAlgorithmId = 0 + X509MDMd5 shared.MessageDigestAlgorithmId = 1 + X509MDSha1 shared.MessageDigestAlgorithmId = 2 + // X509MDRipeMD160 shared.MessageDigestAlgorithmId = 3 x509 package does not support RIPEMD160 + X509MDSha256 shared.MessageDigestAlgorithmId = 8 + X509MDSha384 shared.MessageDigestAlgorithmId = 9 + X509MDSha512 shared.MessageDigestAlgorithmId = 10 ) const ( @@ -58,74 +63,67 @@ const ( ) func NewCommandProcessor() *CommandProcessor { - cryptoSystems := make(map[shared.CryptoSystemId]*CryptoSystem) - cryptoSystems[CsX509] = &CryptoSystem{ - Name: "X.509", - Roots: map[shared.CryptoSystemRootId]*RootCredentials{ - X509RootDefault: { - Name: "openssl", - PrivateKeyFile: "/srv/ca/CA/private/ca.key.pem", - CertificateFile: "/srv/ca/CA/ca.crt.pem", - DatabaseFile: "/srv/ca/CA/index.txt", - CRLNumber: "/srv/ca/CA/crlnumber", + settings := NewCommandProcessorSettings() + + cryptoSystems := map[shared.CryptoSystemId]*CryptoSystem{ + CsX509: { + Name: "X.509", + Roots: map[shared.CryptoSystemRootId]interface{}{ + X509RootDefault: x509_ops.NewRoot(settings.CABaseDir, "openssl", "CA", X509RootDefault), + X509RootClass3: x509_ops.NewRoot(settings.CABaseDir, "class3", "class3", X509RootClass3), + X509RootClass3s: &x509_ops.Root{Name: "class3s"}, + X509Root3: &x509_ops.Root{Name: "root3"}, + X509Root4: &x509_ops.Root{Name: "root4"}, + X509Root5: &x509_ops.Root{Name: "root5"}, }, - X509RootClass3: { - Name: "class3", - PrivateKeyFile: "/srv/ca/class3/private/ca.key.pem", - CertificateFile: "/srv/ca/class3/ca.crt.pem", - DatabaseFile: "/srv/ca/class3/index.txt", - CRLNumber: "/srv/ca/class3/crlnumber", + Profiles: map[shared.CertificateProfileId]interface{}{ + X509ProfileClient: &x509_ops.Profile{Name: "client"}, + X509ProfileClientOrg: &x509_ops.Profile{Name: "client-org"}, + X509ProfileClientCodesign: &x509_ops.Profile{Name: "client-codesign"}, + X509ProfileClientMachine: &x509_ops.Profile{Name: "client-machine"}, + X509ProfileClientAds: &x509_ops.Profile{Name: "client-ads"}, + X509ProfileServer: &x509_ops.Profile{Name: "server"}, + X509ProfileServerOrg: &x509_ops.Profile{Name: "server-org"}, + X509ProfileServerJabber: &x509_ops.Profile{Name: "server-jabber"}, + X509ProfileOCSP: &x509_ops.Profile{Name: "ocsp"}, + X509ProfileTimestamp: &x509_ops.Profile{Name: "timestamp"}, + X509ProfileProxy: &x509_ops.Profile{Name: "proxy"}, + X509ProfileSubCA: &x509_ops.Profile{Name: "subca"}, + }, + // constants for openssl invocations. Should be replaced with + // something more useful + DigestAlgorithms: map[shared.MessageDigestAlgorithmId]interface{}{ + X509MDDefault: x509.SHA256WithRSA, + X509MDMd5: x509.MD5WithRSA, + X509MDSha1: x509.SHA1WithRSA, + X509MDSha256: x509.SHA256WithRSA, + X509MDSha384: x509.SHA384WithRSA, + X509MDSha512: x509.SHA512WithRSA, }, - X509RootClass3s: {Name: "class3s"}, - X509Root3: {Name: "root3"}, - X509Root4: {Name: "root4"}, - X509Root5: {Name: "root5"}, }, - Profiles: map[shared.CertificateProfileId]string{ - X509ProfileClient: "client", - X509ProfileClientOrg: "client-org", - X509ProfileClientCodesign: "client-codesign", - X509ProfileClientMachine: "client-machine", - X509ProfileClientAds: "client-ads", - X509ProfileServer: "server", - X509ProfileServerOrg: "server-org", - X509ProfileServerJabber: "server-jabber", - X509ProfileOCSP: "ocsp", - X509ProfileTimestamp: "timestamp", - X509ProfileProxy: "proxy", - X509ProfileSubCA: "subca", - }, - // constants for openssl invocations. Should be replaced with - // something more useful - DigestAlgorithms: map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm{ - X509MDDefault: x509.SHA256WithRSA, - X509MDMd5: x509.MD5WithRSA, - X509MDSha1: x509.SHA1WithRSA, - X509MDRipeMD160: x509.UnknownSignatureAlgorithm, - X509MDSha256: x509.SHA256WithRSA, - X509MDSha384: x509.SHA384WithRSA, - X509MDSha512: x509.SHA512WithRSA, + CsOpenPGP: { + Name: "OpenPGP", + Roots: map[shared.CryptoSystemRootId]interface{}{ + OpenPGPRoot0: &openpgp_ops.OpenPGPRoot{ + Name: "OpenPGP Root", + SecretKeyRing: path.Join( + settings.OpenPGPKeyRingDir, + fmt.Sprintf("gpg_root_%d", OpenPGPRoot0), + "secring.gpg", + ), + Identifier: settings.OpenPGPUidEmail, + }, + }, + Profiles: map[shared.CertificateProfileId]interface{}{ + OpenPGPDefaultProfile: &openpgp_ops.OpenPGPProfile{Name: "default"}, + }, + // constants for gnupg cert-digest-algo parameter. Should be replaced with + // something more useful + DigestAlgorithms: map[shared.MessageDigestAlgorithmId]interface{}{ + OpenPGPDefaultMD: crypto.SHA256, + }, }, } - cryptoSystems[CsOpenPGP] = &CryptoSystem{ - Name: "OpenPGP", - Roots: map[shared.CryptoSystemRootId]*RootCredentials{ - OpenPGPRoot0: { - Name: "OpenPGP Root", - PrivateKeyFile: "secring0.gpg", - PublicKeyFile: "pubring0.gpg", - }, - }, - Profiles: map[shared.CertificateProfileId]string{ - OpenPGPDefaultProfile: "default", - }, - // constants for gnupg cert-digest-algo parameter. Should be replaced with - // something more useful - DigestAlgorithms: map[shared.MessageDigestAlgorithmId]x509.SignatureAlgorithm{ - OpenPGPDefaultMD: x509.SHA256WithRSA, - }, - } - - return &CommandProcessor{CryptoSystems: cryptoSystems, Settings: NewCommandProcessorSettings()} + return &CommandProcessor{CryptoSystems: cryptoSystems, Settings: settings} } diff --git a/signer/x509_ops/operations.go b/signer/x509_ops/operations.go new file mode 100644 index 0000000..407e281 --- /dev/null +++ b/signer/x509_ops/operations.go @@ -0,0 +1,418 @@ +package x509_ops + +import ( + "bufio" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/hex" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + log "github.com/sirupsen/logrus" + + "git.cacert.org/cacert-gosigner/shared" + "git.cacert.org/cacert-gosigner/signer/common" +) + +const crlLifetime = time.Hour * 24 * 7 + +type Root struct { + Name string + privateKeyFile string + certificateFile string + databaseFile string + crlNumberFile string + crlFileName string + crlHashDir string +} + +func (x *Root) String() string { + return x.Name +} + +type Profile struct { + Name string +} + +func (x *Root) loadCertificate() (*x509.Certificate, error) { + certificateFile := x.certificateFile + pemBytes, err := ioutil.ReadFile(certificateFile) + if err != nil { + return nil, fmt.Errorf( + "could not load certificate %s: %v", + certificateFile, + err, + ) + } + pemBlock, _ := pem.Decode(pemBytes) + if pemBlock.Type != "CERTIFICATE" { + log.Warnf( + "PEM in %s is probably not a certificate. PEM block has type %s", + certificateFile, + pemBlock.Type, + ) + } + certificate, err := x509.ParseCertificate(pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf( + "could no parse certificate from %s: %v", + certificateFile, + err, + ) + } + return certificate, nil +} + +func (x *Root) getPrivateKey() (crypto.Signer, error) { + privateKeyFile := x.privateKeyFile + pemBytes, err := ioutil.ReadFile(privateKeyFile) + if err != nil { + return nil, fmt.Errorf( + "could not load private key %s: %v", + privateKeyFile, + err, + ) + } + pemBlock, _ := pem.Decode(pemBytes) + if pemBlock.Type != "PRIVATE KEY" { + log.Warnf( + "PEM in %s is probably not a private key. PEM block has type %s", + privateKeyFile, + pemBlock.Type, + ) + } + privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf( + "could no parse private key from %s: %v", + privateKeyFile, + err, + ) + } + return privateKey.(*rsa.PrivateKey), nil +} + +func (x *Root) getNextCRLNumber() (*big.Int, error) { + crlNumberFile := x.crlNumberFile + _, err := os.Stat(crlNumberFile) + if err != nil { + log.Warnf("CRL number file %s does not exist: %v", crlNumberFile, err) + return big.NewInt(1), nil + } + data, err := ioutil.ReadFile(crlNumberFile) + if err != nil { + return nil, fmt.Errorf("could not read CRL number file %s", crlNumberFile) + } + result, err := common.StringAsBigInt(data) + if err != nil { + return nil, fmt.Errorf("could not parse content of %s as CRL number: %v", crlNumberFile, err) + } + return result, nil +} + +func (x *Root) bumpCRLNumber(current *big.Int) error { + serial := current.Int64() + 1 + crlNumberFile := x.crlNumberFile + outFile, err := ioutil.TempFile(path.Dir(crlNumberFile), "*.txt") + defer func() { _ = outFile.Close() }() + + _, err = outFile.WriteString(fmt.Sprintf( + "%s\n", + strings.ToUpper(strconv.FormatInt(serial, 16)), + )) + if err != nil { + return fmt.Errorf("could not write new CRL number %d to %s: %v", serial, outFile.Name(), err) + } + + if err = outFile.Close(); err != nil { + return fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) + } + if err = os.Rename(crlNumberFile, fmt.Sprintf("%s.old", crlNumberFile)); err != nil { + return fmt.Errorf("could not rename %s to %s.old: %v", crlNumberFile, crlNumberFile, err) + } + if err = os.Rename(outFile.Name(), crlNumberFile); err != nil { + return fmt.Errorf("could not rename %s to %s: %v", outFile.Name(), crlNumberFile, err) + } + return nil +} + +func (x *Root) loadRevokedCertificatesFromDatabase() ([]pkix.RevokedCertificate, error) { + databaseFile := x.databaseFile + _, err := os.Stat(databaseFile) + if err != nil { + log.Warnf("openssl certificate database file %s does not exist: %v", databaseFile, err) + return []pkix.RevokedCertificate{}, nil + } + file, err := os.Open(databaseFile) + if err != nil { + return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err) + } + defer func() { _ = file.Close() }() + + result := make([]pkix.RevokedCertificate, 0) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.Split(scanner.Text(), "\t") + if line[0] == "R" { + serialNumber, err := common.StringAsBigInt([]byte(line[3])) + if err != nil { + return nil, fmt.Errorf("could not parse serial number %s as big int: %v", line[3], err) + } + revokeTs, err := strconv.ParseInt(line[2][:len(line[2])-1], 10, 64) + result = append(result, pkix.RevokedCertificate{ + SerialNumber: serialNumber, + RevocationTime: time.Unix(revokeTs, 0), + Extensions: nil, + }) + } + } + return result, nil +} + +func (x *Root) recordRevocation(certificate *x509.Certificate) (*pkix.RevokedCertificate, error) { + databaseFile := x.databaseFile + _, err := os.Stat(databaseFile) + if err != nil { + return nil, fmt.Errorf("openssl certificate database file %s does not exist: %v", databaseFile, err) + } + inFile, err := os.Open(databaseFile) + if err != nil { + return nil, fmt.Errorf("could not open openssl certificate database file %s: %v", databaseFile, err) + } + defer func() { _ = inFile.Close() }() + + outFile, err := ioutil.TempFile(path.Dir(databaseFile), "*.txt") + defer func() { _ = outFile.Close() }() + + scanner := bufio.NewScanner(inFile) + writer := bufio.NewWriter(outFile) + + found := false + revocationTime := time.Now() + + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, "\t") + serialNumber, err := common.StringAsBigInt([]byte(parts[3])) + if err != nil { + return nil, fmt.Errorf("could not parse serial number %s as big int: %v", parts[3], err) + } + if serialNumber == certificate.SerialNumber { + line = strings.Join( + []string{"R", parts[1], strconv.FormatInt(revocationTime.Unix(), 10) + "Z", parts[3], parts[4]}, + "\t", + ) + found = true + } + if _, err = writer.WriteString(fmt.Sprintf("%s\n", line)); err != nil { + return nil, fmt.Errorf("could not write '%s' to %s: %v", line, outFile.Name(), err) + } + } + + if err = outFile.Close(); err != nil { + return nil, fmt.Errorf("could not close temporary file %s: %v", outFile.Name(), err) + } + if err = inFile.Close(); err != nil { + return nil, fmt.Errorf("could not close %s: %v", databaseFile, err) + } + + if err = os.Rename(databaseFile, fmt.Sprintf("%s.old", databaseFile)); err != nil { + return nil, fmt.Errorf("could not rename %s to %s.old: %v", databaseFile, databaseFile, err) + } + + if err = os.Rename(outFile.Name(), databaseFile); err != nil { + return nil, fmt.Errorf("could not rename temporary file %s to %s: %v", outFile.Name(), databaseFile, err) + } + + if !found { + log.Warnf("entry not found in database") + } + + return &pkix.RevokedCertificate{ + SerialNumber: certificate.SerialNumber, + RevocationTime: revocationTime, + }, nil +} + +func (x *Root) RevokeCertificate(request []byte) (*pkix.RevokedCertificate, error) { + pemBlock, _ := pem.Decode(request) + if pemBlock.Type != "CERTIFICATE" { + if pemBlock.Type != "CERTIFICATE" { + log.Warnf( + "PEM structure is probably not a certificate. PEM block has type %s", + pemBlock.Type, + ) + log.Trace(request) + } + } + certificate, err := x509.ParseCertificate(pemBlock.Bytes) + if err != nil { + return nil, fmt.Errorf( + "could no parse certificate: %v", + err, + ) + } + return x.recordRevocation(certificate) +} + +func (x *Root) GenerateCrl(algorithm x509.SignatureAlgorithm) ([]byte, *[20]byte, error) { + caCertificate, err := x.loadCertificate() + if err != nil { + return nil, nil, err + } + caPrivateKey, err := x.getPrivateKey() + if err != nil { + return nil, nil, err + } + certificatesToRevoke, err := x.loadRevokedCertificatesFromDatabase() + if err != nil { + return nil, nil, err + } + nextCrlNumber, err := x.getNextCRLNumber() + if err != nil { + return nil, nil, err + } + crlTemplate := &x509.RevocationList{ + SignatureAlgorithm: algorithm, + RevokedCertificates: certificatesToRevoke, + Number: nextCrlNumber, + ThisUpdate: time.Now(), + NextUpdate: time.Now().Add(crlLifetime), + ExtraExtensions: nil, + } + + defer func() { + if err = x.bumpCRLNumber(nextCrlNumber); err != nil { + log.Errorf("could not bump CRL number: %v", err) + } + }() + + crlBytes, err := x509.CreateRevocationList( + rand.Reader, + crlTemplate, + caCertificate, + caPrivateKey, + ) + if err != nil { + return nil, nil, fmt.Errorf("could not create new CRL: %v", err) + } + + if err = ioutil.WriteFile(x.crlFileName, crlBytes, 0644); err != nil { + return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", x.crlFileName, err) + } + + newCrlHash := sha1.Sum(crlBytes) + hashedCrlFileName := path.Join( + x.crlHashDir, + fmt.Sprintf("%s.crl", hex.EncodeToString(newCrlHash[:])), + ) + if err = ioutil.WriteFile(hashedCrlFileName, crlBytes, 0644); err != nil { + return nil, nil, fmt.Errorf("could not write new CRL to %s: %v", hashedCrlFileName, err) + } + + return crlBytes, &newCrlHash, nil +} + +func (x *Root) DeleteOldCRLs(keepHashes ...string) error { + log.Debugf("will look for CRLs in %s", x.crlHashDir) + + found, err := filepath.Glob(path.Join(x.crlHashDir, "*.crl")) + if err != nil { + log.Warnf("could not match files: %v", err) + return nil + } + +nextFound: + for _, filename := range found { + for _, keepHash := range keepHashes { + if x.GetCrlFileName(keepHash) == filename { + continue nextFound + } + } + if err := os.Remove(filename); err != nil { + return fmt.Errorf("could not delete %s: %v", filename, err) + } + } + + return nil +} + +func (x *Root) GetCrlFileName(hash string) string { + return path.Join(x.crlHashDir, fmt.Sprintf("%s.crl", hash)) +} + +func (x *Root) checkPreconditions() { + results := []bool{ + x.checkFile(x.privateKeyFile, "private key"), + x.checkFile(x.certificateFile, "certificate"), + x.checkFile(x.databaseFile, "database"), + x.checkFile(x.crlNumberFile, "CRL serial number"), + x.checkFile(x.crlFileName, "CRL file"), + x.checkDir(x.crlHashDir, "directory for hash indexed CRLs"), + } + for _, success := range results { + if !success { + log.Warnf("preconditions for %s failed, operations may fail too", x) + break + } + } +} + +func (x *Root) checkFile(path, prefix string) bool { + ok := true + if s, e := os.Stat(path); e != nil { + log.Warnf("%s file %s of %s has issues: %v", prefix, path, x, e) + ok = false + } else if s.IsDir() { + log.Warnf("%s file %s of %s is a directory", prefix, path, x) + ok = false + } + return ok +} + +func (x *Root) checkDir(path, prefix string) bool { + ok := true + if s, e := os.Stat(path); e != nil { + log.Warnf("%s %s of %s has issues: %v", prefix, path, x, e) + if err := os.MkdirAll(path, 0755); err != nil { + log.Warnf("could not create %s %s of %s: %v", prefix, path, x, err) + ok = false + } + ok = false + } else if !s.IsDir() { + log.Warnf("%s %s of %s is not a directory", prefix, path, x) + ok = false + } + + return ok +} + +func NewRoot(basedir, name, subdir string, id shared.CryptoSystemRootId) *Root { + root := &Root{ + Name: name, + privateKeyFile: path.Join(basedir, subdir, "private", "ca.key.pem"), + certificateFile: path.Join(basedir, subdir, "ca.crt.pem"), + databaseFile: path.Join(basedir, subdir, "index.txt"), + crlNumberFile: path.Join(basedir, subdir, "crlnumber"), + crlFileName: fmt.Sprintf("revoke-root%d.crl", id), + crlHashDir: path.Join( + basedir, + "currentcrls", + fmt.Sprintf("%d", id), + ), + } + root.checkPreconditions() + return root +}