Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does signing a certificate require `-CAcreateserial` argument?

For example,

openssl x509 \
    -req -sha256 \
    -days "365" \
    -CAcreateserial \
    -CA "ca.crt" -CAkey "ca.key" -passin "pass:abcd" \
    -in "csr.csr" -extfile "ext.ext" \
    -out "c.crt";

It also creates a file ca.srl which contains signed certificate's serial.

The above won't work if -CAcreateserial argument is absent and outputs an error:

/test/ca.srl: No such file or directory
140413509251520:error:06067099:digital envelope routines:EVP_PKEY_copy_parameters:different parameters:../crypto/evp/p_lib.c:93:
140413509251520:error:02001002:system library:fopen:No such file or directory:../crypto/bio/bss_file.c:72:fopen('/test/ca.srl','r')
140413509251520:error:2006D080:BIO routines:BIO_new_file:no such file:../crypto/bio/bss_file.c:79:

Isn't that argument used to output a file with a serial which is possible to get via a command below anyways?

openssl x509 \
    -in "c.crt" \
    -noout \
    -serial;

What's the point? Why does it not create the file internally if required, but saves it on a storage?

like image 254
Faither Avatar asked Dec 30 '22 16:12

Faither


2 Answers

The -serial option of your second command just outputs the serial number of an existing certificate. But when you're signing a certificate the CA needs to generate a unique serial number for each certificate, and until it does that, there's no serial number for -serial to output yet.

Since the serial number for each certificate needs to be unique for each issuer, an issuer needs to keep track of which serial numbers it has used before, to make sure it doesn't reuse any. OpenSSL gives you a simple way to keep track of this using a serial number file. When you specify -CAcreateserial, it'll assign the serial number 01 to the signed certificate, and then create this serial number file with the next serial number (02) in it. On future signing operations, you should be using -CAserial with the name of that file, and not -CAcreateserial, and OpenSSL will increment the value in that file for each certificate signed. In this way, you can sign a bunch of certificates with one issuer certificate, and all their serial numbers will be unique.

If you're using multiple issuer certificates, then you can use a separate serial number file for each one.

Note that while this approach works, it's unsuitable for production use, as there are some problems with using strictly sequentially increasing certificate serial numbers.

like image 57
Crowman Avatar answered Jan 02 '23 05:01

Crowman


Thanks to the @Crowman's answer, I created a script which might demonstrate the issue:

#! /usr/bin/env bash

# -------------------------- #
# User variables             #
# -------------------------- #

certificateAuthorityName="certificate_authority";
certificateAuthorityPrivateKeyPassword="";

certificateName="certificate";
certificateTestPostfix="_test";

# -------------------------- #
# Variables                  #
# -------------------------- #

stdout='/dev/null';
stderr='/dev/null';
certificateAuthorityFilepath="$( pwd )/${certificateAuthorityName}";
certificateFilepath="$( pwd )/${certificateName}";

# -------------------------- #
# Functions                  #
# -------------------------- #

function OR
{
    declare i;

    for (( i=0; i < "$1"; i++ ));
    do
        printf '%s' "$2";
    done;
}

function SignedCertificateCreate
{
    declare createSerial_l="-CAserial ${certificateAuthorityFilepath}.srl";

    if [ "$1" = "1" ];
    then
        rm "${certificateAuthorityFilepath}.srl" > "/dev/null" 2>&1;
        createSerial_l='-CAcreateserial';
    fi

    if ! openssl x509 \
        -req -sha256 \
        -days "365" \
        $createSerial_l \
        -CA "${certificateAuthorityFilepath}.crt" -CAkey "${certificateAuthorityFilepath}.key" -passin "pass:${certificateAuthorityPrivateKeyPassword}" \
        -in "${certificateFilepath}.csr" -extfile "${certificateFilepath}.ext" \
        -out "${certificateFilepath}${certificateTestPostfix}.crt" \
        > "$stdout" 2> "$stderr";
    then
        printf $'Couldn\'t create a certificate\n';

        return 1;
    fi
}

function SignedCertificatesCreate
{
    declare loops_l="1";

    if [ "$1" != "" ];
    then
        loops_l="$1";
    fi

    declare i;

    for (( i=0; i < "$loops_l"; i++ ));
    do
        if ! SignedCertificateCreate "$2";
        then
            return 1;
        fi
    done
}

function ReadCertificateSerialFromFile
{
    cat "${certificateAuthorityFilepath}.srl" 2> "$stderr";
}

# -------------------------- #
# Methods                    #
# -------------------------- #

function Help
{
    echo
    printf ' Description: OpenSSL serial generation test\n\n';
    printf ' Additional arguments:\n\n';
    printf '   -d - Enable certain output for debugging\n';
    printf '   -h - Help message\n\n';
}

function Main
{
    if [[ "$1" == *"h"* ]];
    then
        Help;
        exit 0;
    fi

    if [[ "$1" == *"d"* ]];
    then
        stdout='/dev/stdout';
        stderr='/dev/stderr';
    fi

    if [ ! -f "${certificateAuthorityFilepath}.key" ];
    then
        printf $'There\'s no Certificate Authority private key: %s\n' "${certificateAuthorityFilepath}.key";
        exit 1;
    fi

    if [ "$certificateAuthorityPrivateKeyPassword" = "" ];
    then
        echo
        read -s -p " [ ? ] Certificate Authority Private Key Password: " certificateAuthorityPrivateKeyPassword;
        echo
    fi

    printf '\n  %s \n' "$( OR 80 "-" )";
    printf ' | %35s | ' 'A new {serial #1} generated';
    SignedCertificateCreate 1 || exit 2;
    printf '%40s |\n' "$( ReadCertificateSerialFromFile )"
    printf ' | %35s | ' 'A new {serial #2} generated';
    SignedCertificateCreate 1 || exit 3;
    printf '%40s |\n' "$( ReadCertificateSerialFromFile )"
    printf ' | %35s | ' 'Used the same {serial #2} 5 times';
    SignedCertificatesCreate 5 || exit 4;
    printf '%40s |\n' "$( ReadCertificateSerialFromFile )"
    printf ' | %35s | ' 'Used the same {serial #2} 256 times';
    SignedCertificatesCreate 256 || exit 5;
    printf '%40s |\n' "$( ReadCertificateSerialFromFile )"
    printf '  %s \n\n' "$( OR 80 "-" )";
}

# -------------------------- #
# Main                       #
# -------------------------- #

Main "$@";

A possible script's result:

  --------------------------------------------------------------------------------
 |         A new {serial #1} generated | 310BD94F916BDF47913249966B85A9F7771D746A |
 |         A new {serial #2} generated | 0AC91627E4E99612D3CC7D99BB9793445CEDB36B |
 |   Used the same {serial #2} 5 times | 0AC91627E4E99612D3CC7D99BB9793445CEDB370 |
 | Used the same {serial #2} 256 times | 0AC91627E4E99612D3CC7D99BB9793445CEDB470 |
  --------------------------------------------------------------------------------

Just to clarify, the {serial #2} output differs (the very endings):

  • ...B36Bhex or 45931dec (0);
  • ...B370hex or 45936dec (+5);
  • ...B470hex or 46192dec (+256).
like image 33
Faither Avatar answered Jan 02 '23 06:01

Faither